[stable/2023.2] [ATMOSPHERE-483] fix: Directly import converted image to RBD (#2071)

This is an automated cherry-pick of #2000
/assign ricolin
diff --git a/images/cinder/patches/cinder/0002-Directly-import-converted-image-to-RBD.patch b/images/cinder/patches/cinder/0002-Directly-import-converted-image-to-RBD.patch
new file mode 100644
index 0000000..f63c4e9
--- /dev/null
+++ b/images/cinder/patches/cinder/0002-Directly-import-converted-image-to-RBD.patch
@@ -0,0 +1,152 @@
+From ddb4c87da6d20cc2cf37db70498f6890db1b2498 Mon Sep 17 00:00:00 2001
+From: ricolin <rlin@vexxhost.com>
+Date: Tue, 22 Oct 2024 18:31:52 +0800
+Subject: [PATCH] Directly import converted image to RBD
+
+For volume encryption from Glance image case,
+once we cloned the image down, we do convert and import back to RBD.
+
+This patch allows us to avoid another tempfile write and directly
+upload image to RBD.
+
+Related-Bug: #2055517
+Change-Id: Ib5e15eeee6a02e2833d14ac34f6fdeb4a6548a67
+---
+ cinder/tests/unit/volume/drivers/test_rbd.py  |  2 -
+ cinder/volume/drivers/rbd.py                  | 57 ++++++++-----------
+ ...verted-encrypt-image-005986a59d1027e1.yaml |  9 +++
+ 3 files changed, 34 insertions(+), 34 deletions(-)
+ create mode 100644 releasenotes/notes/allow-direct-import-converted-encrypt-image-005986a59d1027e1.yaml
+
+diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
+index 13f67b3d4..291db326d 100644
+--- a/cinder/volume/drivers/rbd.py
++++ b/cinder/volume/drivers/rbd.py
+@@ -32,7 +32,6 @@ from oslo_log import log as logging
+ from oslo_service import loopingcall
+ from oslo_utils import encodeutils
+ from oslo_utils import excutils
+-from oslo_utils import fileutils
+ from oslo_utils import units
+ try:
+     import rados
+@@ -1992,11 +1991,10 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+         self._copy_image_to_volume(context, volume, image_service, image_id,
+                                    disable_sparse=disable_sparse)
+ 
+-    def _encrypt_image(self,
+-                       context: context.RequestContext,
+-                       volume: Volume,
+-                       tmp_dir: str,
+-                       src_image_path: Any) -> None:
++    def _encrypt_image_and_upload(
++        self, context: context.RequestContext,
++        volume: Volume, tmp_dir: str, src_image_path: Any
++    ) -> None:
+         encryption = volume_utils.check_encryption_provider(
+             volume,
+             context)
+@@ -2010,6 +2008,19 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+         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 += f':id={user_id}'
++        if conf:
++            rbd_options += f':conf={conf}'
++
++        rbd_dest = 'rbd:%(pool_name)s/%(image_name)s%(rbd_options)s' % {
++            'pool_name': self.configuration.rbd_pool,
++            'image_name': volume.name,
++            'rbd_options': rbd_options
++        }
++
+         tmp_dir = volume_utils.image_conversion_dir()
+ 
+         with tempfile.NamedTemporaryFile(prefix='luks_',
+@@ -2017,18 +2028,10 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+             with open(pass_file.name, 'w') as f:
+                 f.write(passphrase)
+ 
+-            # Convert the raw image to luks
+-            dest_image_path = src_image_path + '.luks'
+-            try:
+-                image_utils.convert_image(src_image_path, dest_image_path,
+-                                          'luks', src_format='raw',
+-                                          cipher_spec=cipher_spec,
+-                                          passphrase_file=pass_file.name)
+-
+-                # Replace the original image with the now encrypted image
+-                os.rename(dest_image_path, src_image_path)
+-            finally:
+-                fileutils.delete_if_exists(dest_image_path)
++            image_utils.convert_image(src_image_path, rbd_dest,
++                                      'luks', src_format='raw',
++                                      cipher_spec=cipher_spec,
++                                      passphrase_file=pass_file.name)
+ 
+     def _copy_image_to_volume(self,
+                               context: context.RequestContext,
+@@ -2047,9 +2050,6 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+                                      size=volume.size,
+                                      disable_sparse=disable_sparse)
+ 
+-            if encrypted:
+-                self._encrypt_image(context, volume, tmp_dir, tmp.name)
+-
+             @utils.retry(exception.VolumeIsBusy,
+                          self.configuration.rados_connection_interval,
+                          self.configuration.rados_connection_retries)
+@@ -2058,17 +2058,21 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
+ 
+             _delete_volume(volume)
+ 
+-            chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
+-            order = int(math.log(chunk_size, 2))
+-            # keep using the command line import instead of librbd since it
+-            # detects zeroes to preserve sparseness in the image
+-            args = ['rbd', 'import',
+-                    '--pool', self.configuration.rbd_pool,
+-                    '--order', order,
+-                    tmp.name, volume.name,
+-                    '--new-format']
+-            args.extend(self._ceph_args())
+-            self._try_execute(*args)
++            if encrypted:
++                self._encrypt_image_and_upload(
++                    context, volume, tmp_dir, tmp.name)
++            else:
++                chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
++                order = int(math.log(chunk_size, 2))
++                # keep using the command line import instead of librbd since it
++                # detects zeroes to preserve sparseness in the image
++                args = ['rbd', 'import',
++                        '--pool', self.configuration.rbd_pool,
++                        '--order', order,
++                        tmp.name, volume.name,
++                        '--new-format']
++                args.extend(self._ceph_args())
++                self._try_execute(*args)
+         self._resize(volume)
+         # We may need to re-enable replication because we have deleted the
+         # original image and created a new one using the command line import.
+diff --git a/releasenotes/notes/allow-direct-import-converted-encrypt-image-005986a59d1027e1.yaml b/releasenotes/notes/allow-direct-import-converted-encrypt-image-005986a59d1027e1.yaml
+new file mode 100644
+index 000000000..b1544214d
+--- /dev/null
++++ b/releasenotes/notes/allow-direct-import-converted-encrypt-image-005986a59d1027e1.yaml
+@@ -0,0 +1,9 @@
++---
++fixes:
++  - |
++    [Bug 255517](https://bugs.launchpad.net/cinder/+bug/2055517): Improve slow
++    on create encrypted volumes with temp file import.
++    For volume encryption from Glance image case, once we cloned the image
++    down, we do convert and import back to RBD. We now avoid another tempfile
++    write and directly upload image to RBD. And it is now directly upload to
++    RBD with qemu-img command without temprory converted image file generated.
+-- 
+2.25.1
+