--- /dev/null
+From fd7e9dd59ffa346bed5a11e5312f4bb1bf114ab4 Mon Sep 17 00:00:00 2001
+From: Dmitry Borodaenko <angdraug@gmail.com>
+Date: Wed, 27 Nov 2013 14:33:00 -0800
+Subject: [PATCH] Do not clone non-raw images in rbd backend
+
+RBD backend only supports booting from images in raw format. A volume
+that was cloned from an image in any other format is not bootable. The
+RBD driver will consider non-raw images to be uncloneable to trigger
+automatic conversion to raw format.
+
+Includes conversion of the corresponding unit test to use mock (instead
+of mox) and expanded comments and error messages from patchset #58893 by
+Edward Hope-Morley.
+
+Change-Id: I5725d2f7576bc1b3e9b874ba944ad17d33a6e2cb
+Closes-Bug: #1246219
+Closes-Bug: #1247998
+---
+ cinder/tests/test_gpfs.py | 6 +-
+ cinder/tests/test_netapp_nfs.py | 12 +-
+ cinder/tests/test_rbd.py | 168 +++++++++++++++++---------
+ cinder/tests/test_volume.py | 2 +-
+ cinder/volume/driver.py | 7 +-
+ cinder/volume/drivers/gpfs.py | 2 +-
+ cinder/volume/drivers/lvm.py | 2 +-
+ cinder/volume/drivers/netapp/nfs.py | 2 +-
+ cinder/volume/drivers/rbd.py | 14 ++-
+ cinder/volume/drivers/scality.py | 2 +-
+ cinder/volume/flows/create_volume/__init__.py | 2 +-
+ 11 files changed, 144 insertions(+), 75 deletions(-)
+
+diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py
+index 4fdb788..1f47c6b 100644
+--- a/cinder/tests/test_gpfs.py
++++ b/cinder/tests/test_gpfs.py
+@@ -288,7 +288,8 @@ class GPFSDriverTestCase(test.TestCase):
+ CONF.gpfs_images_share_mode = 'copy_on_write'
+ self.driver.clone_image(volume,
+ None,
+- self.image_id)
++ self.image_id,
++ {})
+
+ self.assertTrue(os.path.exists(volumepath))
+ self.volume.delete_volume(self.context, volume['id'])
+@@ -309,7 +310,8 @@ class GPFSDriverTestCase(test.TestCase):
+ CONF.gpfs_images_share_mode = 'copy'
+ self.driver.clone_image(volume,
+ None,
+- self.image_id)
++ self.image_id,
++ {})
+
+ self.assertTrue(os.path.exists(volumepath))
+ self.volume.delete_volume(self.context, volume['id'])
+diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py
+index 950efc8..042280e 100644
+--- a/cinder/tests/test_netapp_nfs.py
++++ b/cinder/tests/test_netapp_nfs.py
+@@ -469,7 +469,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+ drv._post_clone_image(volume)
+
+ mox.ReplayAll()
+- drv. clone_image(volume, ('image_location', None), 'image_id')
++ drv.clone_image(volume, ('image_location', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def get_img_info(self, format):
+@@ -493,7 +493,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ (prop, cloned) = drv. clone_image(
+- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ if not cloned and not prop['provider_location']:
+ pass
+@@ -529,7 +529,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ drv. clone_image(
+- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def test_clone_image_cloneableshare_notraw(self):
+@@ -566,7 +566,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def test_clone_image_file_not_discovered(self):
+@@ -605,7 +605,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+@@ -652,7 +652,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py
+index 60de09b..054bbb5 100644
+--- a/cinder/tests/test_rbd.py
++++ b/cinder/tests/test_rbd.py
+@@ -34,6 +34,7 @@ from cinder.tests.test_volume import DriverTestCase
+ from cinder import units
+ from cinder.volume import configuration as conf
+ import cinder.volume.drivers.rbd as driver
++from cinder.volume.flows import create_volume
+
+
+ LOG = logging.getLogger(__name__)
+@@ -247,7 +248,8 @@ class RBDTestCase(test.TestCase):
+ self.assertRaises(exception.ImageUnacceptable,
+ self.driver._parse_location,
+ loc)
+- self.assertFalse(self.driver._is_cloneable(loc))
++ self.assertFalse(
++ self.driver._is_cloneable(loc, {'disk_format': 'raw'}))
+
+ def test_cloneable(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+@@ -264,12 +266,14 @@ class RBDTestCase(test.TestCase):
+
+ self.mox.ReplayAll()
+
+- self.assertTrue(self.driver._is_cloneable(location))
++ self.assertTrue(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
+
+ def test_uncloneable_different_fsid(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+ location = 'rbd://def/pool/image/snap'
+- self.assertFalse(self.driver._is_cloneable(location))
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
+
+ def test_uncloneable_unreadable(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+@@ -284,7 +288,16 @@ class RBDTestCase(test.TestCase):
+
+ self.mox.ReplayAll()
+
+- self.assertFalse(self.driver._is_cloneable(location))
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
++
++ def test_uncloneable_bad_format(self):
++ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
++ location = 'rbd://abc/pool/image/snap'
++ formats = ['qcow2', 'vmdk', 'vdi']
++ for f in formats:
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': f}))
+
+ def _copy_image(self):
+ @contextlib.contextmanager
+@@ -504,26 +517,37 @@ class ManagedRBDTestCase(DriverTestCase):
+ super(ManagedRBDTestCase, self).setUp()
+ fake_image.stub_out_image_service(self.stubs)
+ self.volume.driver.set_initialized()
++ self.called = []
+
+- def _clone_volume_from_image(self, expected_status,
+- clone_works=True):
++ def _create_volume_from_image(self, expected_status, raw=False,
++ clone_error=False):
+ """Try to clone a volume from an image, and check the status
+ afterwards.
++
++ NOTE: if clone_error is True we force the image type to raw otherwise
++ clone_image is not called
+ """
+- def fake_clone_image(volume, image_location, image_id):
+- return {'provider_location': None}, True
++ def mock_clone_image(volume, image_location, image_id, image_meta):
++ self.called.append('clone_image')
++ if clone_error:
++ raise exception.CinderException()
++ else:
++ return {'provider_location': None}, True
+
+- def fake_clone_error(volume, image_location, image_id):
+- raise exception.CinderException()
++ if clone_error:
++ raw = True
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- if clone_works:
+- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_image)
++ # See tests.image.fake for image types.
++ if raw:
++ expected_calls = ['clone_image']
++ image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
+ else:
+- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_error)
++ expected_calls = ['clone_image', 'create_volume',
++ 'copy_image_to_volume']
++ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+
+- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+ volume_id = 1
++
+ # creating volume testdata
+ db.volume_create(self.context,
+ {'id': volume_id,
+@@ -533,58 +557,88 @@ class ManagedRBDTestCase(DriverTestCase):
+ 'status': 'creating',
+ 'instance_uuid': None,
+ 'host': 'dummy'})
+- try:
+- if clone_works:
+- self.volume.create_volume(self.context,
+- volume_id,
+- image_id=image_id)
+- else:
+- self.assertRaises(exception.CinderException,
+- self.volume.create_volume,
+- self.context,
+- volume_id,
+- image_id=image_id)
+-
+- volume = db.volume_get(self.context, volume_id)
+- self.assertEqual(volume['status'], expected_status)
+- finally:
+- # cleanup
+- db.volume_destroy(self.context, volume_id)
++
++ with mock.patch.object(self.volume.driver, 'create_volume') as \
++ mock_create_volume:
++ with mock.patch.object(self.volume.driver, 'clone_image',
++ mock_clone_image):
++ with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
++ '_copy_image_to_volume') as \
++ mock_copy_image_to_volume:
++ self.volume.driver._is_cloneable = mock.Mock()
++ self.volume.driver._is_cloneable.return_value = True
++
++ try:
++ if not clone_error:
++ self.volume.create_volume(self.context,
++ volume_id,
++ image_id=image_id)
++ else:
++ self.assertRaises(exception.CinderException,
++ self.volume.create_volume,
++ self.context,
++ volume_id,
++ image_id=image_id)
++
++ volume = db.volume_get(self.context, volume_id)
++ self.assertEqual(volume['status'], expected_status)
++ finally:
++ # cleanup
++ db.volume_destroy(self.context, volume_id)
++
++ if raw:
++ self.assertEquals(self.called, ['clone_image'])
++
++ mock_create_volume.assert_called()
++ mock_copy_image_to_volume.assert_called()
+
+ def test_create_vol_from_image_status_available(self):
+- """Verify that before cloning, an image is in the available state."""
+- self._clone_volume_from_image('available', True)
++ """Clone raw image then verify volume is in available state."""
++ self._create_volume_from_image('available', raw=True)
++
++ def test_create_vol_from_non_raw_image_status_available(self):
++ """Clone non-raw image then verify volume is in available state."""
++ self._create_volume_from_image('available', raw=False)
+
+ def test_create_vol_from_image_status_error(self):
+- """Verify that before cloning, an image is in the available state."""
+- self._clone_volume_from_image('error', False)
++ """Clone raw image with failure then verify volume is in error
++ state.
++ """
++ self._create_volume_from_image('error', raw=True, clone_error=True)
+
+ def test_clone_image(self):
+- # Test Failure Case(s)
+- expected = ({}, False)
++ driver = self.volume.driver
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: False)
+- image_loc = (object(), object())
+- actual = self.volume.driver.clone_image(object(), image_loc, object())
+- self.assertEqual(expected, actual)
++ # Test uncloneable case(s)
++ with mock.patch.object(driver, '_is_cloneable',
++ lambda *args: False) as mock_is_cloneable:
++ image_loc = (mock.Mock(), mock.Mock())
++ actual = driver.clone_image(mock.Mock(), image_loc,
++ mock.Mock(), {})
++ self.assertEqual(({}, False), actual)
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- self.assertEqual(expected,
+- self.volume.driver.clone_image(object(), None, None))
++ self.assertEqual(({}, False),
++ driver.clone_image(object(), None, None, {}))
+
+- # Test Success Case(s)
++ # Test success case(s)
+ expected = ({'provider_location': None}, True)
+-
+- self.stubs.Set(self.volume.driver, '_parse_location',
+- lambda x: ('a', 'b', 'c', 'd'))
+-
+- self.stubs.Set(self.volume.driver, '_clone', lambda *args: None)
+- self.stubs.Set(self.volume.driver, '_resize', lambda *args: None)
+- actual = self.volume.driver.clone_image(object(), image_loc, object())
+- self.assertEqual(expected, actual)
++ mpo = mock.patch.object
++ with mpo(driver, '_is_cloneable', lambda *args: True):
++ with mpo(driver, '_parse_location',
++ lambda x: ('a', 'b', 'c', 'd')):
++ with mpo(driver, '_clone') as mock_clone:
++ with mpo(driver, '_resize') as mock_resize:
++ actual = driver.clone_image(mock.Mock(), image_loc,
++ mock.Mock(),
++ {'disk_format': 'raw'})
++ self.assertEqual(expected, actual)
++ mock_clone.assert_called()
++ mock_resize.assert_called()
+
+ def test_clone_success(self):
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- self.stubs.Set(self.volume.driver, 'clone_image', lambda a, b, c: True)
++ self.stubs.Set(self.volume.driver, '_is_cloneable', lambda *args: True)
++ self.stubs.Set(self.volume.driver, 'clone_image',
++ lambda a, b, c, d: True)
+ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+- self.assertTrue(self.volume.driver.clone_image({}, image_id, image_id))
++ self.assertTrue(self.volume.driver.clone_image({},
++ image_id, image_id, {}))
+diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py
+index 4a0d475..928799f 100644
+--- a/cinder/tests/test_volume.py
++++ b/cinder/tests/test_volume.py
+@@ -1216,7 +1216,7 @@ class VolumeTestCase(BaseVolumeTestCase):
+ def fake_fetch_to_raw(ctx, image_service, image_id, path, size=None):
+ pass
+
+- def fake_clone_image(volume_ref, image_location, image_id):
++ def fake_clone_image(volume_ref, image_location, image_id, image_meta):
+ return {'provider_location': None}, True
+
+ dst_fd, dst_path = tempfile.mkstemp()
+diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
+index 21dd12b..dd2be3c 100644
+--- a/cinder/volume/driver.py
++++ b/cinder/volume/driver.py
+@@ -396,7 +396,7 @@ class VolumeDriver(object):
+ connector.disconnect_volume(attach_info['conn']['data'],
+ attach_info['device'])
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+@@ -407,6 +407,11 @@ class VolumeDriver(object):
+ It can be used by the driver to introspect internal
+ stores or registry to do an efficient image clone.
+
++ image_meta is a dictionary that includes 'disk_format' (e.g.
++ raw, qcow2) and other image attributes that allow drivers to
++ decide whether they can clone the image without first requiring
++ conversion.
++
+ Returns a dict of volume properties eg. provider_location,
+ boolean indicating whether cloning occurred
+ """
+diff --git a/cinder/volume/drivers/gpfs.py b/cinder/volume/drivers/gpfs.py
+index 9a1a397..8792ad8 100644
+--- a/cinder/volume/drivers/gpfs.py
++++ b/cinder/volume/drivers/gpfs.py
+@@ -463,7 +463,7 @@ class GPFSDriver(driver.VolumeDriver):
+ return '100M'
+ return '%sG' % size_in_g
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ return self._clone_image(volume, image_location, image_id)
+
+ def _is_cloneable(self, image_id):
+diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py
+index 094436f..c6023eb 100644
+--- a/cinder/volume/drivers/lvm.py
++++ b/cinder/volume/drivers/lvm.py
+@@ -322,7 +322,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
+ finally:
+ self.delete_snapshot(temp_snapshot)
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ return None, False
+
+ def backup_volume(self, context, backup, backup_service):
+diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py
+index 602a1dc..9463137 100644
+--- a/cinder/volume/drivers/netapp/nfs.py
++++ b/cinder/volume/drivers/netapp/nfs.py
+@@ -374,7 +374,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
+ LOG.warning(_('Exception during deleting %s'), ex.__str__())
+ return False
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
+index 775ab16..7ed59d0 100644
+--- a/cinder/volume/drivers/rbd.py
++++ b/cinder/volume/drivers/rbd.py
+@@ -706,7 +706,7 @@ class RBDDriver(driver.VolumeDriver):
+ with RADOSClient(self) as client:
+ return client.cluster.get_fsid()
+
+- def _is_cloneable(self, image_location):
++ def _is_cloneable(self, image_location, image_meta):
+ try:
+ fsid, pool, image, snapshot = self._parse_location(image_location)
+ except exception.ImageUnacceptable as e:
+@@ -718,6 +718,13 @@ class RBDDriver(driver.VolumeDriver):
+ LOG.debug(reason)
+ return False
+
++ if image_meta['disk_format'] != 'raw':
++ reason = _("rbd image clone requires image format to be "
++ "'raw' but image {0} is '{1}'").format(
++ image_location, image_meta['disk_format'])
++ LOG.debug(reason)
++ return False
++
+ # check that we can read the image
+ try:
+ with RBDVolumeProxy(self, image,
+@@ -730,9 +737,10 @@ class RBDDriver(driver.VolumeDriver):
+ dict(loc=image_location, err=e))
+ return False
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ image_location = image_location[0] if image_location else None
+- if image_location is None or not self._is_cloneable(image_location):
++ 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)
+diff --git a/cinder/volume/drivers/scality.py b/cinder/volume/drivers/scality.py
+index 4cf49c6..abd6c29 100644
+--- a/cinder/volume/drivers/scality.py
++++ b/cinder/volume/drivers/scality.py
+@@ -250,7 +250,7 @@ class ScalityDriver(driver.VolumeDriver):
+ image_meta,
+ self.local_path(volume))
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+diff --git a/cinder/volume/flows/create_volume/__init__.py b/cinder/volume/flows/create_volume/__init__.py
+index bb7acd3..e5aa371 100644
+--- a/cinder/volume/flows/create_volume/__init__.py
++++ b/cinder/volume/flows/create_volume/__init__.py
+@@ -1438,7 +1438,7 @@ class CreateVolumeFromSpecTask(base.CinderTask):
+ # dict containing provider_location for cloned volume
+ # and clone status.
+ model_update, cloned = self.driver.clone_image(
+- volume_ref, image_location, image_id)
++ volume_ref, image_location, image_id, image_meta)
+ if not cloned:
+ # TODO(harlowja): what needs to be rolled back in the clone if this
+ # volume create fails?? Likely this should be a subflow or broken
+--
+1.8.5.1
+
fix-sqlalchemy-requirements.patch
fix-babel-requirements.patch
+MIRA-Do-not-clone-non-raw-images-in-rbd-backend.patch
--- /dev/null
+From fd7e9dd59ffa346bed5a11e5312f4bb1bf114ab4 Mon Sep 17 00:00:00 2001
+From: Dmitry Borodaenko <angdraug@gmail.com>
+Date: Wed, 27 Nov 2013 14:33:00 -0800
+Subject: [PATCH] Do not clone non-raw images in rbd backend
+
+RBD backend only supports booting from images in raw format. A volume
+that was cloned from an image in any other format is not bootable. The
+RBD driver will consider non-raw images to be uncloneable to trigger
+automatic conversion to raw format.
+
+Includes conversion of the corresponding unit test to use mock (instead
+of mox) and expanded comments and error messages from patchset #58893 by
+Edward Hope-Morley.
+
+Change-Id: I5725d2f7576bc1b3e9b874ba944ad17d33a6e2cb
+Closes-Bug: #1246219
+Closes-Bug: #1247998
+---
+ cinder/tests/test_gpfs.py | 6 +-
+ cinder/tests/test_netapp_nfs.py | 12 +-
+ cinder/tests/test_rbd.py | 168 +++++++++++++++++---------
+ cinder/tests/test_volume.py | 2 +-
+ cinder/volume/driver.py | 7 +-
+ cinder/volume/drivers/gpfs.py | 2 +-
+ cinder/volume/drivers/lvm.py | 2 +-
+ cinder/volume/drivers/netapp/nfs.py | 2 +-
+ cinder/volume/drivers/rbd.py | 14 ++-
+ cinder/volume/drivers/scality.py | 2 +-
+ cinder/volume/flows/create_volume/__init__.py | 2 +-
+ 11 files changed, 144 insertions(+), 75 deletions(-)
+
+diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py
+index 4fdb788..1f47c6b 100644
+--- a/cinder/tests/test_gpfs.py
++++ b/cinder/tests/test_gpfs.py
+@@ -288,7 +288,8 @@ class GPFSDriverTestCase(test.TestCase):
+ CONF.gpfs_images_share_mode = 'copy_on_write'
+ self.driver.clone_image(volume,
+ None,
+- self.image_id)
++ self.image_id,
++ {})
+
+ self.assertTrue(os.path.exists(volumepath))
+ self.volume.delete_volume(self.context, volume['id'])
+@@ -309,7 +310,8 @@ class GPFSDriverTestCase(test.TestCase):
+ CONF.gpfs_images_share_mode = 'copy'
+ self.driver.clone_image(volume,
+ None,
+- self.image_id)
++ self.image_id,
++ {})
+
+ self.assertTrue(os.path.exists(volumepath))
+ self.volume.delete_volume(self.context, volume['id'])
+diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py
+index 950efc8..042280e 100644
+--- a/cinder/tests/test_netapp_nfs.py
++++ b/cinder/tests/test_netapp_nfs.py
+@@ -469,7 +469,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+ drv._post_clone_image(volume)
+
+ mox.ReplayAll()
+- drv. clone_image(volume, ('image_location', None), 'image_id')
++ drv.clone_image(volume, ('image_location', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def get_img_info(self, format):
+@@ -493,7 +493,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ (prop, cloned) = drv. clone_image(
+- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ if not cloned and not prop['provider_location']:
+ pass
+@@ -529,7 +529,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ drv. clone_image(
+- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def test_clone_image_cloneableshare_notraw(self):
+@@ -566,7 +566,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+
+ def test_clone_image_file_not_discovered(self):
+@@ -605,7 +605,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+@@ -652,7 +652,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
++ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py
+index 60de09b..054bbb5 100644
+--- a/cinder/tests/test_rbd.py
++++ b/cinder/tests/test_rbd.py
+@@ -34,6 +34,7 @@ from cinder.tests.test_volume import DriverTestCase
+ from cinder import units
+ from cinder.volume import configuration as conf
+ import cinder.volume.drivers.rbd as driver
++from cinder.volume.flows import create_volume
+
+
+ LOG = logging.getLogger(__name__)
+@@ -247,7 +248,8 @@ class RBDTestCase(test.TestCase):
+ self.assertRaises(exception.ImageUnacceptable,
+ self.driver._parse_location,
+ loc)
+- self.assertFalse(self.driver._is_cloneable(loc))
++ self.assertFalse(
++ self.driver._is_cloneable(loc, {'disk_format': 'raw'}))
+
+ def test_cloneable(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+@@ -264,12 +266,14 @@ class RBDTestCase(test.TestCase):
+
+ self.mox.ReplayAll()
+
+- self.assertTrue(self.driver._is_cloneable(location))
++ self.assertTrue(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
+
+ def test_uncloneable_different_fsid(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+ location = 'rbd://def/pool/image/snap'
+- self.assertFalse(self.driver._is_cloneable(location))
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
+
+ def test_uncloneable_unreadable(self):
+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
+@@ -284,7 +288,16 @@ class RBDTestCase(test.TestCase):
+
+ self.mox.ReplayAll()
+
+- self.assertFalse(self.driver._is_cloneable(location))
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
++
++ def test_uncloneable_bad_format(self):
++ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
++ location = 'rbd://abc/pool/image/snap'
++ formats = ['qcow2', 'vmdk', 'vdi']
++ for f in formats:
++ self.assertFalse(
++ self.driver._is_cloneable(location, {'disk_format': f}))
+
+ def _copy_image(self):
+ @contextlib.contextmanager
+@@ -504,26 +517,37 @@ class ManagedRBDTestCase(DriverTestCase):
+ super(ManagedRBDTestCase, self).setUp()
+ fake_image.stub_out_image_service(self.stubs)
+ self.volume.driver.set_initialized()
++ self.called = []
+
+- def _clone_volume_from_image(self, expected_status,
+- clone_works=True):
++ def _create_volume_from_image(self, expected_status, raw=False,
++ clone_error=False):
+ """Try to clone a volume from an image, and check the status
+ afterwards.
++
++ NOTE: if clone_error is True we force the image type to raw otherwise
++ clone_image is not called
+ """
+- def fake_clone_image(volume, image_location, image_id):
+- return {'provider_location': None}, True
++ def mock_clone_image(volume, image_location, image_id, image_meta):
++ self.called.append('clone_image')
++ if clone_error:
++ raise exception.CinderException()
++ else:
++ return {'provider_location': None}, True
+
+- def fake_clone_error(volume, image_location, image_id):
+- raise exception.CinderException()
++ if clone_error:
++ raw = True
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- if clone_works:
+- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_image)
++ # See tests.image.fake for image types.
++ if raw:
++ expected_calls = ['clone_image']
++ image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
+ else:
+- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_error)
++ expected_calls = ['clone_image', 'create_volume',
++ 'copy_image_to_volume']
++ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+
+- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+ volume_id = 1
++
+ # creating volume testdata
+ db.volume_create(self.context,
+ {'id': volume_id,
+@@ -533,58 +557,88 @@ class ManagedRBDTestCase(DriverTestCase):
+ 'status': 'creating',
+ 'instance_uuid': None,
+ 'host': 'dummy'})
+- try:
+- if clone_works:
+- self.volume.create_volume(self.context,
+- volume_id,
+- image_id=image_id)
+- else:
+- self.assertRaises(exception.CinderException,
+- self.volume.create_volume,
+- self.context,
+- volume_id,
+- image_id=image_id)
+-
+- volume = db.volume_get(self.context, volume_id)
+- self.assertEqual(volume['status'], expected_status)
+- finally:
+- # cleanup
+- db.volume_destroy(self.context, volume_id)
++
++ with mock.patch.object(self.volume.driver, 'create_volume') as \
++ mock_create_volume:
++ with mock.patch.object(self.volume.driver, 'clone_image',
++ mock_clone_image):
++ with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
++ '_copy_image_to_volume') as \
++ mock_copy_image_to_volume:
++ self.volume.driver._is_cloneable = mock.Mock()
++ self.volume.driver._is_cloneable.return_value = True
++
++ try:
++ if not clone_error:
++ self.volume.create_volume(self.context,
++ volume_id,
++ image_id=image_id)
++ else:
++ self.assertRaises(exception.CinderException,
++ self.volume.create_volume,
++ self.context,
++ volume_id,
++ image_id=image_id)
++
++ volume = db.volume_get(self.context, volume_id)
++ self.assertEqual(volume['status'], expected_status)
++ finally:
++ # cleanup
++ db.volume_destroy(self.context, volume_id)
++
++ if raw:
++ self.assertEquals(self.called, ['clone_image'])
++
++ mock_create_volume.assert_called()
++ mock_copy_image_to_volume.assert_called()
+
+ def test_create_vol_from_image_status_available(self):
+- """Verify that before cloning, an image is in the available state."""
+- self._clone_volume_from_image('available', True)
++ """Clone raw image then verify volume is in available state."""
++ self._create_volume_from_image('available', raw=True)
++
++ def test_create_vol_from_non_raw_image_status_available(self):
++ """Clone non-raw image then verify volume is in available state."""
++ self._create_volume_from_image('available', raw=False)
+
+ def test_create_vol_from_image_status_error(self):
+- """Verify that before cloning, an image is in the available state."""
+- self._clone_volume_from_image('error', False)
++ """Clone raw image with failure then verify volume is in error
++ state.
++ """
++ self._create_volume_from_image('error', raw=True, clone_error=True)
+
+ def test_clone_image(self):
+- # Test Failure Case(s)
+- expected = ({}, False)
++ driver = self.volume.driver
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: False)
+- image_loc = (object(), object())
+- actual = self.volume.driver.clone_image(object(), image_loc, object())
+- self.assertEqual(expected, actual)
++ # Test uncloneable case(s)
++ with mock.patch.object(driver, '_is_cloneable',
++ lambda *args: False) as mock_is_cloneable:
++ image_loc = (mock.Mock(), mock.Mock())
++ actual = driver.clone_image(mock.Mock(), image_loc,
++ mock.Mock(), {})
++ self.assertEqual(({}, False), actual)
+
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- self.assertEqual(expected,
+- self.volume.driver.clone_image(object(), None, None))
++ self.assertEqual(({}, False),
++ driver.clone_image(object(), None, None, {}))
+
+- # Test Success Case(s)
++ # Test success case(s)
+ expected = ({'provider_location': None}, True)
+-
+- self.stubs.Set(self.volume.driver, '_parse_location',
+- lambda x: ('a', 'b', 'c', 'd'))
+-
+- self.stubs.Set(self.volume.driver, '_clone', lambda *args: None)
+- self.stubs.Set(self.volume.driver, '_resize', lambda *args: None)
+- actual = self.volume.driver.clone_image(object(), image_loc, object())
+- self.assertEqual(expected, actual)
++ mpo = mock.patch.object
++ with mpo(driver, '_is_cloneable', lambda *args: True):
++ with mpo(driver, '_parse_location',
++ lambda x: ('a', 'b', 'c', 'd')):
++ with mpo(driver, '_clone') as mock_clone:
++ with mpo(driver, '_resize') as mock_resize:
++ actual = driver.clone_image(mock.Mock(), image_loc,
++ mock.Mock(),
++ {'disk_format': 'raw'})
++ self.assertEqual(expected, actual)
++ mock_clone.assert_called()
++ mock_resize.assert_called()
+
+ def test_clone_success(self):
+- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
+- self.stubs.Set(self.volume.driver, 'clone_image', lambda a, b, c: True)
++ self.stubs.Set(self.volume.driver, '_is_cloneable', lambda *args: True)
++ self.stubs.Set(self.volume.driver, 'clone_image',
++ lambda a, b, c, d: True)
+ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
+- self.assertTrue(self.volume.driver.clone_image({}, image_id, image_id))
++ self.assertTrue(self.volume.driver.clone_image({},
++ image_id, image_id, {}))
+diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py
+index 4a0d475..928799f 100644
+--- a/cinder/tests/test_volume.py
++++ b/cinder/tests/test_volume.py
+@@ -1216,7 +1216,7 @@ class VolumeTestCase(BaseVolumeTestCase):
+ def fake_fetch_to_raw(ctx, image_service, image_id, path, size=None):
+ pass
+
+- def fake_clone_image(volume_ref, image_location, image_id):
++ def fake_clone_image(volume_ref, image_location, image_id, image_meta):
+ return {'provider_location': None}, True
+
+ dst_fd, dst_path = tempfile.mkstemp()
+diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
+index 21dd12b..dd2be3c 100644
+--- a/cinder/volume/driver.py
++++ b/cinder/volume/driver.py
+@@ -396,7 +396,7 @@ class VolumeDriver(object):
+ connector.disconnect_volume(attach_info['conn']['data'],
+ attach_info['device'])
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+@@ -407,6 +407,11 @@ class VolumeDriver(object):
+ It can be used by the driver to introspect internal
+ stores or registry to do an efficient image clone.
+
++ image_meta is a dictionary that includes 'disk_format' (e.g.
++ raw, qcow2) and other image attributes that allow drivers to
++ decide whether they can clone the image without first requiring
++ conversion.
++
+ Returns a dict of volume properties eg. provider_location,
+ boolean indicating whether cloning occurred
+ """
+diff --git a/cinder/volume/drivers/gpfs.py b/cinder/volume/drivers/gpfs.py
+index 9a1a397..8792ad8 100644
+--- a/cinder/volume/drivers/gpfs.py
++++ b/cinder/volume/drivers/gpfs.py
+@@ -463,7 +463,7 @@ class GPFSDriver(driver.VolumeDriver):
+ return '100M'
+ return '%sG' % size_in_g
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ return self._clone_image(volume, image_location, image_id)
+
+ def _is_cloneable(self, image_id):
+diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py
+index 094436f..c6023eb 100644
+--- a/cinder/volume/drivers/lvm.py
++++ b/cinder/volume/drivers/lvm.py
+@@ -322,7 +322,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
+ finally:
+ self.delete_snapshot(temp_snapshot)
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ return None, False
+
+ def backup_volume(self, context, backup, backup_service):
+diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py
+index 602a1dc..9463137 100644
+--- a/cinder/volume/drivers/netapp/nfs.py
++++ b/cinder/volume/drivers/netapp/nfs.py
+@@ -374,7 +374,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
+ LOG.warning(_('Exception during deleting %s'), ex.__str__())
+ return False
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
+index 775ab16..7ed59d0 100644
+--- a/cinder/volume/drivers/rbd.py
++++ b/cinder/volume/drivers/rbd.py
+@@ -706,7 +706,7 @@ class RBDDriver(driver.VolumeDriver):
+ with RADOSClient(self) as client:
+ return client.cluster.get_fsid()
+
+- def _is_cloneable(self, image_location):
++ def _is_cloneable(self, image_location, image_meta):
+ try:
+ fsid, pool, image, snapshot = self._parse_location(image_location)
+ except exception.ImageUnacceptable as e:
+@@ -718,6 +718,13 @@ class RBDDriver(driver.VolumeDriver):
+ LOG.debug(reason)
+ return False
+
++ if image_meta['disk_format'] != 'raw':
++ reason = _("rbd image clone requires image format to be "
++ "'raw' but image {0} is '{1}'").format(
++ image_location, image_meta['disk_format'])
++ LOG.debug(reason)
++ return False
++
+ # check that we can read the image
+ try:
+ with RBDVolumeProxy(self, image,
+@@ -730,9 +737,10 @@ class RBDDriver(driver.VolumeDriver):
+ dict(loc=image_location, err=e))
+ return False
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ image_location = image_location[0] if image_location else None
+- if image_location is None or not self._is_cloneable(image_location):
++ 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)
+diff --git a/cinder/volume/drivers/scality.py b/cinder/volume/drivers/scality.py
+index 4cf49c6..abd6c29 100644
+--- a/cinder/volume/drivers/scality.py
++++ b/cinder/volume/drivers/scality.py
+@@ -250,7 +250,7 @@ class ScalityDriver(driver.VolumeDriver):
+ image_meta,
+ self.local_path(volume))
+
+- def clone_image(self, volume, image_location, image_id):
++ def clone_image(self, volume, image_location, image_id, image_meta):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+diff --git a/cinder/volume/flows/create_volume/__init__.py b/cinder/volume/flows/create_volume/__init__.py
+index bb7acd3..e5aa371 100644
+--- a/cinder/volume/flows/create_volume/__init__.py
++++ b/cinder/volume/flows/create_volume/__init__.py
+@@ -1438,7 +1438,7 @@ class CreateVolumeFromSpecTask(base.CinderTask):
+ # dict containing provider_location for cloned volume
+ # and clone status.
+ model_update, cloned = self.driver.clone_image(
+- volume_ref, image_location, image_id)
++ volume_ref, image_location, image_id, image_meta)
+ if not cloned:
+ # TODO(harlowja): what needs to be rolled back in the clone if this
+ # volume create fails?? Likely this should be a subflow or broken
+--
+1.8.5.1
+
Patch0002: 0002-Use-updated-parallel-install-versions-of-epel-packag.patch
Patch0003: 0003-Remove-runtime-dep-on-python-pbr-python-d2to1.patch
Patch0004: 0004-Revert-Use-oslo.sphinx-and-remove-local-copy-of-doc-.patch
+Patch0005: MIRA-Do-not-clone-non-raw-images-in-rbd-backend.patch
BuildArch: noarch
BuildRequires: intltool
%patch0002 -p1
%patch0003 -p1
%patch0004 -p1
+%patch0005 -p1
find . \( -name .gitignore -o -name .placeholder \) -delete