[stable/zed] chore: add cinder rbd tunning patches to zed (#1139)
relate to #1016
diff --git a/images/cinder/Dockerfile b/images/cinder/Dockerfile
index f954b3a..4d5addf 100644
--- a/images/cinder/Dockerfile
+++ b/images/cinder/Dockerfile
@@ -18,6 +18,8 @@
ARG CINDER_GIT_REF=f74e2729554bee01b0a3e631a8001bb39e540433
ADD --keep-git-dir=true https://opendev.org/openstack/cinder.git#${CINDER_GIT_REF} /src/cinder
RUN git -C /src/cinder fetch --unshallow
+COPY patches/cinder /patches/cinder
+RUN git -C /src/cinder apply --verbose /patches/cinder/*
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip,sharing=private <<EOF bash -xe
pip3 install \
--constraint /upper-constraints.txt \
diff --git a/images/cinder/patches/cinder/0001-Create-encrypted-volumes-directly-to-RBD.patch b/images/cinder/patches/cinder/0001-Create-encrypted-volumes-directly-to-RBD.patch
new file mode 100644
index 0000000..fd8370a
--- /dev/null
+++ b/images/cinder/patches/cinder/0001-Create-encrypted-volumes-directly-to-RBD.patch
@@ -0,0 +1,146 @@
+From 96cca9076fb95b2fae7dbf254d49f492ce02b69a Mon Sep 17 00:00:00 2001
+From: ricolin <ricolin@ricolky.com>
+Date: Fri, 1 Mar 2024 13:50:13 +0800
+Subject: [PATCH 1/3] Create encrypted volumes directly to RBD
+
+This fix slow on create encrypted volumes with temp file import.
+Encrypted volume create is now directly upload to RBD with qemu-img
+command without temprory image file generated.
+
+Closes-Bug: #2055517
+Change-Id: If7a72a4acd5600de1350289a9d9c38017d42659e
+---
+ cinder/tests/unit/volume/drivers/test_rbd.py | 9 +--
+ cinder/volume/drivers/rbd.py | 62 +++++++++----------
+ ...ate-encrypted-volume-c1bb6b44b85c0242.yaml | 7 +++
+ 3 files changed, 40 insertions(+), 38 deletions(-)
+ create mode 100644 releasenotes/notes/improve-create-encrypted-volume-c1bb6b44b85c0242.yaml
+
+diff --git a/cinder/tests/unit/volume/drivers/test_rbd.py b/cinder/tests/unit/volume/drivers/test_rbd.py
+index 57dae3af1..38ceaf27f 100644
+--- a/cinder/tests/unit/volume/drivers/test_rbd.py
++++ b/cinder/tests/unit/volume/drivers/test_rbd.py
+@@ -3247,7 +3247,6 @@ class RBDTestCase(test.TestCase):
+ self.__dict__ = d
+
+ mock_temp_file.return_value.__enter__.side_effect = [
+- DictObj({'name': '/imgfile'}),
+ DictObj({'name': '/passfile'})]
+
+ key_mgr = fake_keymgr.fake_api()
+@@ -3268,15 +3267,13 @@ class RBDTestCase(test.TestCase):
+ self.context)
+ mock_open.assert_called_with('/passfile', 'w')
+
+- mock_exec.assert_any_call(
++ mock_exec.assert_called_with(
+ 'qemu-img', 'create', '-f', 'luks', '-o',
+ 'cipher-alg=aes-256,cipher-mode=xts,ivgen-alg=essiv',
+ '--object',
+ 'secret,id=luks_sec,format=raw,file=/passfile',
+- '-o', 'key-secret=luks_sec', '/imgfile', '12288M')
+- mock_exec.assert_any_call(
+- 'rbd', 'import', '--dest-pool', 'rbd', '--order', 22,
+- '/imgfile', self.volume_c.name)
++ '-o', 'key-secret=luks_sec', 'rbd:rbd/%s' % self.volume_c.name,
++ '12288M')
+
+ @mock.patch('cinder.objects.Volume.get_by_id')
+ @mock.patch('cinder.db.volume_glance_metadata_get', return_value={})
+diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
+index 6cc86c2c5..b883ee47a 100644
+--- a/cinder/volume/drivers/rbd.py
++++ b/cinder/volume/drivers/rbd.py
+@@ -1087,8 +1087,8 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ context: context.RequestContext) -> None:
+ """Create an encrypted volume.
+
+- This works by creating an encrypted image locally,
+- and then uploading it to the volume.
++ This works by creating an encrypted image and
++ then uploading it to the volume directly.
+ """
+ encryption = volume_utils.check_encryption_provider(volume, context)
+
+@@ -1100,37 +1100,35 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ # create a file
+ tmp_dir = volume_utils.image_conversion_dir()
+
+- with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_image:
+- with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_key:
+- with open(tmp_key.name, 'w') as f:
+- f.write(passphrase)
+-
+- cipher_spec = image_utils.decode_cipher(encryption['cipher'],
+- encryption['key_size'])
+-
+- create_cmd = (
+- 'qemu-img', 'create', '-f', 'luks',
+- '-o', 'cipher-alg=%(cipher_alg)s,'
+- 'cipher-mode=%(cipher_mode)s,'
+- 'ivgen-alg=%(ivgen_alg)s' % cipher_spec,
+- '--object', 'secret,id=luks_sec,'
+- 'format=raw,file=%(passfile)s' % {'passfile':
+- tmp_key.name},
+- '-o', 'key-secret=luks_sec',
+- tmp_image.name,
+- '%sM' % (volume.size * 1024))
+- self._execute(*create_cmd)
+-
+- # Copy image into RBD
+- chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
+- order = int(math.log(chunk_size, 2))
++ with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_key:
++ with open(tmp_key.name, 'w') as f:
++ f.write(passphrase)
+
+- cmd = ['rbd', 'import',
+- '--dest-pool', self.configuration.rbd_pool,
+- '--order', order,
+- tmp_image.name, volume.name]
+- cmd.extend(self._ceph_args())
+- self._execute(*cmd)
++ cipher_spec = image_utils.decode_cipher(encryption['cipher'],
++ encryption['key_size'])
++
++ _, conf, user_id, _ = self._get_config_tuple()
++ rbd_options = ''
++ if user_id:
++ rbd_options += ':id=%(user_id)s' % {'user_id': user_id}
++ if conf:
++ rbd_options += ':conf=%(conf)s' % {'conf': conf}
++ create_cmd = (
++ 'qemu-img', 'create', '-f', 'luks',
++ '-o', 'cipher-alg=%(cipher_alg)s,'
++ 'cipher-mode=%(cipher_mode)s,'
++ 'ivgen-alg=%(ivgen_alg)s' % cipher_spec,
++ '--object', 'secret,id=luks_sec,'
++ 'format=raw,file=%(passfile)s' % {'passfile':
++ tmp_key.name},
++ '-o', 'key-secret=luks_sec',
++ 'rbd:%(pool_name)s/%(image_name)s%(rbd_options)s' % {
++ 'pool_name': self.configuration.rbd_pool,
++ 'image_name': volume.name,
++ 'rbd_options': rbd_options
++ },
++ '%sM' % (volume.size * 1024))
++ self._execute(*create_cmd)
+
+ def create_volume(self, volume: Volume) -> dict[str, Any]:
+ """Creates a logical volume."""
+diff --git a/releasenotes/notes/improve-create-encrypted-volume-c1bb6b44b85c0242.yaml b/releasenotes/notes/improve-create-encrypted-volume-c1bb6b44b85c0242.yaml
+new file mode 100644
+index 000000000..8bdff6746
+--- /dev/null
++++ b/releasenotes/notes/improve-create-encrypted-volume-c1bb6b44b85c0242.yaml
+@@ -0,0 +1,7 @@
++---
++fixes:
++ - |
++ [Bug 255517](https://bugs.launchpad.net/cinder/+bug/2055517): Fix slow
++ on create encrypted volumes with temp file import. Encrypted volume create
++ is now directly upload to rbd with qemu-img command without temprory image
++ file generated.
+--
+2.25.1
+
diff --git a/images/cinder/patches/cinder/0002-Allow-clone-encrypted-image-to-encrypted-volume.patch b/images/cinder/patches/cinder/0002-Allow-clone-encrypted-image-to-encrypted-volume.patch
new file mode 100644
index 0000000..60c75d1
--- /dev/null
+++ b/images/cinder/patches/cinder/0002-Allow-clone-encrypted-image-to-encrypted-volume.patch
@@ -0,0 +1,130 @@
+From 410d45106cfc20c064380c3914ff8c2cd41a1a5e Mon Sep 17 00:00:00 2001
+From: ricolin <rlin@vexxhost.com>
+Date: Sat, 16 Mar 2024 00:35:12 +0800
+Subject: [PATCH 2/3] Allow clone encrypted image to encrypted volume
+
+Exactly like what we did in copy-and-import image when create encrypted
+volume from encrypted image. If the image is encrypted, we will copy
+`cinder_encryption_key_id` from image metadata to volume. That means we
+should be safe to try directly clone from encrypted image.
+
+Related-Bug: #2055517
+Change-Id: Id6a1452c2c197a58677bf181470f54565fbd263b
+---
+ .../volume/flows/test_create_volume_flow.py | 46 +++++++++++++++++++
+ cinder/volume/flows/manager/create_volume.py | 9 +++-
+ ...clone-encryped-image-6961ca1439825dc4.yaml | 8 ++++
+ 3 files changed, 61 insertions(+), 2 deletions(-)
+ create mode 100644 releasenotes/notes/allow-clone-encryped-image-6961ca1439825dc4.yaml
+
+diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
+index 5b4ddb35f..7e08f8e78 100644
+--- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py
++++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
+@@ -1202,6 +1202,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
+ encryption_key_id=fakes.ENCRYPTION_KEY_ID,
+ host='host@backend#pool')
+
++ fake_driver.clone_image.return_value = (None, False)
+ fake_image_service = fake_image.FakeImageService()
+ image_meta = {}
+ image_id = fakes.IMAGE_ID
+@@ -1218,6 +1219,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
+ image_meta, fake_image_service)
+
+ fake_driver.create_volume.assert_called_once_with(volume)
++ fake_driver.clone_image.assert_called_once()
+ fake_driver.copy_image_to_encrypted_volume.assert_not_called()
+ fake_driver.copy_image_to_volume.assert_called_once_with(
+ self.ctxt, volume, fake_image_service, image_id)
+@@ -1226,6 +1228,50 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
+ image_meta=image_meta)
+ mock_cleanup_cg.assert_called_once_with(volume)
+
++ @mock.patch('cinder.volume.flows.manager.create_volume.'
++ 'CreateVolumeFromSpecTask.'
++ '_handle_bootable_volume_glance_meta')
++ @mock.patch('cinder.image.image_utils.TemporaryImages.fetch')
++ @mock.patch('cinder.image.image_utils.qemu_img_info')
++ @mock.patch('cinder.image.image_utils.check_virtual_size')
++ def test_create_encrypted_volume_from_enc_image_clone(
++ self, mock_check_size, mock_qemu_img,
++ mock_fetch_img, mock_handle_bootable
++ ):
++ fake_db = mock.MagicMock()
++ fake_driver = mock.MagicMock()
++ fake_volume_manager = mock.MagicMock()
++ fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
++ fake_volume_manager, fake_db, fake_driver)
++ volume = fake_volume.fake_volume_obj(
++ self.ctxt,
++ encryption_key_id=fakes.ENCRYPTION_KEY_ID,
++ host='host@backend#pool')
++
++ fake_driver.clone_image.return_value = (None, True)
++ fake_image_service = fake_image.FakeImageService()
++ image_meta = {}
++ image_id = fakes.IMAGE_ID
++ image_meta['id'] = image_id
++ image_meta['status'] = 'active'
++ image_meta['size'] = 1
++ image_meta['cinder_encryption_key_id'] = \
++ '00000000-0000-0000-0000-000000000000'
++ image_location = 'abc'
++
++ fake_db.volume_update.return_value = volume
++ fake_manager._create_from_image(self.ctxt, volume,
++ image_location, image_id,
++ image_meta, fake_image_service)
++
++ fake_driver.create_volume.assert_not_called()
++ fake_driver.clone_image.assert_called_once()
++ fake_driver.copy_image_to_encrypted_volume.assert_not_called()
++ fake_driver.copy_image_to_volume.assert_not_called()
++ mock_handle_bootable.assert_called_once_with(self.ctxt, volume,
++ image_id=image_id,
++ image_meta=image_meta)
++
+ @ddt.data({'driver_error': True},
+ {'driver_error': False})
+ @mock.patch('cinder.backup.api.API.get_available_backup_service_host')
+diff --git a/cinder/volume/flows/manager/create_volume.py b/cinder/volume/flows/manager/create_volume.py
+index 0ae3cb59d..9d0590d9c 100644
+--- a/cinder/volume/flows/manager/create_volume.py
++++ b/cinder/volume/flows/manager/create_volume.py
+@@ -1087,11 +1087,16 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
+ # dict containing provider_location for cloned volume
+ # and clone status.
+ # NOTE (lixiaoy1): Currently all images are raw data, we can't
+- # use clone_image to copy data if new volume is encrypted.
++ # use clone_image to copy data if new volume is encrypted
++ # NOTE (ricolin): If the image provided an encryption key, we have
++ # already cloned it to the volume's key in
++ # _get_encryption_key_id, so we can do a direct clone.
++ image_encryption_key = image_meta.get('cinder_encryption_key_id')
+ volume_is_encrypted = volume.encryption_key_id is not None
+ cloned = False
+ model_update = None
+- if not volume_is_encrypted:
++ if not volume_is_encrypted or (
++ volume_is_encrypted and image_encryption_key):
+ model_update, cloned = self.driver.clone_image(context,
+ volume,
+ image_location,
+diff --git a/releasenotes/notes/allow-clone-encryped-image-6961ca1439825dc4.yaml b/releasenotes/notes/allow-clone-encryped-image-6961ca1439825dc4.yaml
+new file mode 100644
+index 000000000..d6c7e8eb8
+--- /dev/null
++++ b/releasenotes/notes/allow-clone-encryped-image-6961ca1439825dc4.yaml
+@@ -0,0 +1,8 @@
++---
++features:
++ - |
++ Allow clone encrypted image when create encrypted volume from image.
++ Exactly like what we did in copy-and-import image when create encrypted
++ volume from encrypted image. If the image is encrypted, we will copy
++ `cinder_encryption_key_id` from image metadata to volume. That means we
++ should be safe to try directly clone from encrypted image.
+--
+2.25.1
+
diff --git a/images/cinder/patches/cinder/0003-Allow-encrypted-volume-clone-from-Glance-image.patch b/images/cinder/patches/cinder/0003-Allow-encrypted-volume-clone-from-Glance-image.patch
new file mode 100644
index 0000000..49ad536
--- /dev/null
+++ b/images/cinder/patches/cinder/0003-Allow-encrypted-volume-clone-from-Glance-image.patch
@@ -0,0 +1,320 @@
+From e72198cdf5b63fba70acb3ff43ec0b7531ff76da Mon Sep 17 00:00:00 2001
+From: ricolin <rlin@vexxhost.com>
+Date: Fri, 15 Mar 2024 23:26:14 +0800
+Subject: [PATCH 3/3] Allow encrypted volume clone from Glance image
+
+Allow clone image when creating encrypted volume from Glance image if both
+stored in RBD.
+Previously, Glance image clone is not supported for encrypted volume
+creation. The old process is to download image to local disk, encrypt the
+local file, and import it back to RBD. This not just slow, but also
+protentially take large amount of local disk space from hosts that runs
+Cinder volume service.
+The new process is to try and clone from Glance image (if it's also stored
+in RBD), flatten it, and encrypting new image in RBD for volume. And If
+Glance image source is not clonable, will continue with copy-and-import
+method as previous flow.
+In above flow, If clone from Glance image is appliable. Even it still
+requires to clone and flatten RBD image might took some time, but should
+still be a lot faster than copy-and-import. And also no local disk will
+be used to store raw image in this case.
+This also introduced driver method `clone_image_and_encrypt` for drivers
+that seperate the clone process from non-encrypted volume so the create
+flow won't be affected.
+
+Related-Bug: #2055517
+Change-Id: Ia023646d8bc9468bf5cc8955f7013299b2a3a460
+---
+ .../volume/flows/test_create_volume_flow.py | 49 ++++++++++
+ cinder/volume/driver.py | 11 +++
+ cinder/volume/drivers/rbd.py | 95 ++++++++++++++++---
+ cinder/volume/flows/manager/create_volume.py | 8 +-
+ ...for-encrypted-volume-de477647e9016b8b.yaml | 21 ++++
+ 5 files changed, 167 insertions(+), 17 deletions(-)
+ create mode 100644 releasenotes/notes/allow-clone-image-for-encrypted-volume-de477647e9016b8b.yaml
+
+diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
+index 7e08f8e78..04d9048ea 100644
+--- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py
++++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
+@@ -1164,6 +1164,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
+ image_location = 'abc'
+
+ fake_db.volume_update.return_value = volume
++ fake_driver.clone_image_and_encrypt.return_value = (None, False)
+ fake_manager._create_from_image(self.ctxt, volume,
+ image_location, image_id,
+ image_meta, fake_image_service)
+@@ -1177,6 +1178,54 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
+ image_meta=image_meta)
+ mock_cleanup_cg.assert_called_once_with(volume)
+
++ @mock.patch('cinder.volume.flows.manager.create_volume.'
++ 'CreateVolumeFromSpecTask.'
++ '_prepare_image_cache_entry')
++ @mock.patch('cinder.volume.flows.manager.create_volume.'
++ 'CreateVolumeFromSpecTask.'
++ '_handle_bootable_volume_glance_meta')
++ @mock.patch('cinder.image.image_utils.TemporaryImages.fetch')
++ @mock.patch('cinder.image.image_utils.qemu_img_info')
++ @mock.patch('cinder.image.image_utils.check_virtual_size')
++ def test_create_encrypted_volume_from_image_clone(
++ self, mock_check_size, mock_qemu_img, mock_fetch_img,
++ mock_handle_bootable, mock_prepare_image_cache
++ ):
++ fake_db = mock.MagicMock()
++ fake_driver = mock.MagicMock()
++ fake_volume_manager = mock.MagicMock()
++ fake_cache = mock.MagicMock()
++ fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
++ fake_volume_manager, fake_db, fake_driver, fake_cache)
++ volume = fake_volume.fake_volume_obj(
++ self.ctxt,
++ encryption_key_id=fakes.ENCRYPTION_KEY_ID,
++ host='host@backend#pool')
++
++ fake_image_service = fake_image.FakeImageService()
++ image_meta = {}
++ image_id = fakes.IMAGE_ID
++ image_meta['id'] = image_id
++ image_meta['status'] = 'active'
++ image_meta['size'] = 1
++ image_location = 'abc'
++
++ fake_db.volume_update.return_value = volume
++ fake_driver.clone_image_and_encrypt.return_value = (None, True)
++ fake_manager._create_from_image(self.ctxt, volume,
++ image_location, image_id,
++ image_meta, fake_image_service)
++
++ mock_prepare_image_cache.assert_not_called()
++ fake_driver.create_volume.assert_not_called()
++ fake_driver.clone_image.assert_not_called()
++ fake_driver.clone_image_and_encrypt.assert_called_once()
++ fake_driver.copy_image_to_encrypted_volume.assert_not_called()
++ fake_driver.copy_image_to_volume.assert_not_called()
++ mock_handle_bootable.assert_called_once_with(self.ctxt, volume,
++ image_id=image_id,
++ image_meta=image_meta)
++
+ @mock.patch('cinder.volume.flows.manager.create_volume.'
+ 'CreateVolumeFromSpecTask.'
+ '_cleanup_cg_in_volume')
+diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
+index 0208b8be6..b0b669019 100644
+--- a/cinder/volume/driver.py
++++ b/cinder/volume/driver.py
+@@ -1167,6 +1167,17 @@ class BaseVD(object, metaclass=abc.ABCMeta):
+ """
+ return None, False
+
++ def clone_image_and_encrypt(
++ self, context, volume, image_location, image_meta, image_service
++ ):
++ """Create and encrypt a volume efficiently from an existing image.
++
++ Refer to
++ :obj:`cinder.interface.volume_driver.VolumeDriverCore.clone_image`
++ for additional information.
++ """
++ return None, False
++
+ def backup_use_temp_snapshot(self):
+ """Get the configured setting for backup from snapshot.
+
+diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
+index b883ee47a..855f32896 100644
+--- a/cinder/volume/drivers/rbd.py
++++ b/cinder/volume/drivers/rbd.py
+@@ -141,6 +141,13 @@ CONF.register_opts(RBD_OPTS, group=configuration.SHARED_CONF_GROUP)
+ EXTRA_SPECS_REPL_ENABLED = "replication_enabled"
+ EXTRA_SPECS_MULTIATTACH = "multiattach"
+
++# Note(ricolin): Reference ceph site for more information:
++# https://github.com/ceph/ceph/blob/main/src/include/rbd/librbd.h
++RBD_ENCRYPTION_ALG = {
++ 'aes-128': 0,
++ 'aes-256': 1
++}
++
+ QOS_KEY_MAP = {
+ 'total_iops_sec': {
+ 'ceph_key': 'rbd_qos_iops_limit',
+@@ -1188,6 +1195,20 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+
+ return max(image_stripe_unit, default_stripe_unit)
+
++ def _encrypt_volume(self,
++ context: context.RequestContext,
++ volume: Volume,
++ passphrase: str,
++ cipher_spec: dict
++ ) -> None:
++ LOG.debug("Encrypting volume $s", volume.name)
++ with RBDVolumeProxy(self, volume.name) as vol:
++ vol.encryption_format(
++ 0,
++ passphrase,
++ RBD_ENCRYPTION_ALG[cipher_spec['cipher_alg']]
++ )
++
+ def _clone(self,
+ volume: Volume,
+ src_pool: str,
+@@ -1871,6 +1892,37 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ image_location: Optional[list],
+ image_meta: dict,
+ image_service) -> tuple[dict, bool]:
++ return self._clone_image(context, volume, image_location,
++ image_meta, image_service)
++
++ def clone_image_and_encrypt(
++ self,
++ context: context.RequestContext,
++ volume: Volume,
++ image_location: Optional[list],
++ image_meta: dict,
++ image_service
++ ) -> tuple[dict, bool]:
++
++ # Note(ricolin): method `encryption_format` added after Ceph Pacific
++ # release (>=16.1.0).
++ if self.rbd and hasattr(
++ self.rbd.Image, 'encryption_format') and callable(
++ self.rbd.Image.encryption_format):
++ return self._clone_image(
++ context, volume, image_location,
++ image_meta, image_service, is_encrypt=True)
++ else:
++ return {}, False
++
++ def _clone_image(self,
++ context: context.RequestContext,
++ volume: Volume,
++ image_location: Optional[list],
++ image_meta: dict,
++ image_service,
++ is_encrypt: Optional[bool] = False
++ ) -> tuple[dict, bool]:
+ if image_location:
+ # Note: image_location[0] is glance image direct_url.
+ # image_location[1] contains the list of all locations (including
+@@ -1888,12 +1940,41 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ url_location, image_meta):
+ _prefix, pool, image, snapshot = \
+ self._parse_location(url_location)
++ if is_encrypt:
++ passphrase, cipher_spec = self._fetch_encryption_info(
++ context, volume)
++ if cipher_spec['cipher_alg'] not in RBD_ENCRYPTION_ALG:
++ LOG.debug(
++ "Skip clone. Cipher spec: %s not supported "
++ "for encrypt volume directly from RBD.",
++ cipher_spec)
++ return ({}, False)
+ volume_update = self._clone(volume, pool, image, snapshot)
++ if is_encrypt:
++ self._flatten(self.configuration.rbd_pool, volume.name)
++ self._encrypt_volume(
++ context, volume, passphrase, cipher_spec)
+ volume_update['provider_location'] = None
+ self._resize(volume)
+ return volume_update, True
+ return ({}, False)
+
++ def _fetch_encryption_info(self,
++ context: context.RequestContext,
++ volume: Volume) -> tuple[str, dict]:
++ encryption = volume_utils.check_encryption_provider(
++ volume,
++ context)
++ # Fetch the key associated with the volume and decode the passphrase
++ keymgr = key_manager.API(CONF)
++ key = keymgr.get(context, encryption['encryption_key_id'])
++ passphrase = binascii.hexlify(key.get_encoded()).decode('utf-8')
++
++ # Decode the dm-crypt style cipher spec into something qemu-img can use
++ cipher_spec = image_utils.decode_cipher(encryption['cipher'],
++ encryption['key_size'])
++ return passphrase, cipher_spec
++
+ def copy_image_to_encrypted_volume(self,
+ context: context.RequestContext,
+ volume: Volume,
+@@ -1914,18 +1995,8 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ volume: Volume,
+ tmp_dir: str,
+ src_image_path: Any) -> None:
+- encryption = volume_utils.check_encryption_provider(
+- volume,
+- context)
+-
+- # Fetch the key associated with the volume and decode the passphrase
+- keymgr = key_manager.API(CONF)
+- key = keymgr.get(context, encryption['encryption_key_id'])
+- passphrase = binascii.hexlify(key.get_encoded()).decode('utf-8')
+-
+- # Decode the dm-crypt style cipher spec into something qemu-img can use
+- cipher_spec = image_utils.decode_cipher(encryption['cipher'],
+- encryption['key_size'])
++ passphrase, cipher_spec = self._fetch_encryption_info(
++ context, volume)
+
+ tmp_dir = volume_utils.image_conversion_dir()
+
+diff --git a/cinder/volume/flows/manager/create_volume.py b/cinder/volume/flows/manager/create_volume.py
+index 9d0590d9c..10eaa4b92 100644
+--- a/cinder/volume/flows/manager/create_volume.py
++++ b/cinder/volume/flows/manager/create_volume.py
+@@ -1086,11 +1086,6 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
+ # NOTE (singn): two params need to be returned
+ # dict containing provider_location for cloned volume
+ # and clone status.
+- # NOTE (lixiaoy1): Currently all images are raw data, we can't
+- # use clone_image to copy data if new volume is encrypted
+- # NOTE (ricolin): If the image provided an encryption key, we have
+- # already cloned it to the volume's key in
+- # _get_encryption_key_id, so we can do a direct clone.
+ image_encryption_key = image_meta.get('cinder_encryption_key_id')
+ volume_is_encrypted = volume.encryption_key_id is not None
+ cloned = False
+@@ -1102,6 +1097,9 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
+ image_location,
+ image_meta,
+ image_service)
++ else:
++ model_update, cloned = self.driver.clone_image_and_encrypt(
++ context, volume, image_location, image_meta, image_service)
+
+ # Try and clone the image if we have it set as a glance location.
+ if not cloned and 'cinder' in CONF.allowed_direct_url_schemes:
+diff --git a/releasenotes/notes/allow-clone-image-for-encrypted-volume-de477647e9016b8b.yaml b/releasenotes/notes/allow-clone-image-for-encrypted-volume-de477647e9016b8b.yaml
+new file mode 100644
+index 000000000..63d1f38cd
+--- /dev/null
++++ b/releasenotes/notes/allow-clone-image-for-encrypted-volume-de477647e9016b8b.yaml
+@@ -0,0 +1,21 @@
++---
++features:
++ - |
++ Allow clone image when creating encrypted volume from Glance image if both
++ stored in RBD.
++ Previously, Glance image clone is not supported for encrypted volume
++ creation. The old process is to download image to local disk, encrypt the
++ local file, and import it back to RBD. This not just slow, but also
++ protentially take large amount of local disk space from hosts that runs
++ Cinder volume service.
++ The new process is to try and clone from Glance image (if it's also stored
++ in RBD), flatten it, and encrypting new image in RBD for volume. And If
++ Glance image source is not clonable, will continue with copy-and-import
++ method as previous flow.
++ In above flow, If clone from Glance image is appliable. Even it still
++ requires to clone and flatten RBD image might took some time, but should
++ still be a lot faster than copy-and-import. And also no local disk will
++ be used to store raw image in this case.
++ This also introduced driver method `clone_image_and_encrypt` for drivers
++ that seperate the clone process from non-encrypted volume so the create
++ flow won't be affected.
+--
+2.25.1
+