From ad9f66a7153a5b6358b99351017bda7d5bd50f5d Mon Sep 17 00:00:00 2001
From: Eric Harney <eharney@redhat.com>
Date: Wed, 12 Jul 2023 16:04:59 +0000
Subject: [PATCH] Coerce booleans to integer values in paginate_query

(Grabbed from oslo.db's I4b90f1873 which fixes the same
issue.)

This fixes a failed request when a volume listing is requested
with sorting by bootable:asc and with a marker id specified.

It looks like we are due to replace our paginate_query() method
with the one from oslo.db, as noted in the comments above
the method.

Closes-Bug: #2027532
Change-Id: Idf75e9cc8be6d9f0be320f8109e45b9b9f93dacf
(cherry picked from commit 6d60a6b1ae4c95d4b202746caacd86293ea84620)
---
 cinder/common/sqlalchemyutils.py              |  5 +++-
 cinder/tests/unit/api/v3/test_volumes.py      | 25 ++++++++++++++++---
 ...-sort-by-boolean-fix-49972c69007d5ebc.yaml |  6 +++++
 3 files changed, 32 insertions(+), 4 deletions(-)
 create mode 100644 releasenotes/notes/bug-2027532-volume-list-sort-by-boolean-fix-49972c69007d5ebc.yaml

diff --git a/cinder/common/sqlalchemyutils.py b/cinder/common/sqlalchemyutils.py
index da8ab034d8e..c2db6e361a8 100644
--- a/cinder/common/sqlalchemyutils.py
+++ b/cinder/common/sqlalchemyutils.py
@@ -35,7 +35,8 @@ _TYPE_SCHEMA = {
     'datetime': datetime.datetime(1900, 1, 1),
     'big_integer': 0,
     'integer': 0,
-    'string': ''
+    'string': '',
+    'boolean': False,
 }
 
 
@@ -155,6 +156,8 @@ def paginate_query(query, model, limit, sort_keys, marker=None,
                 *[(model_attr.isnot(None), model_attr)],
                 else_=default,
             )
+            if isinstance(model_attr.type, sqlalchemy.Boolean):
+                marker_values[i] = int(marker_values[i])
             if sort_dirs[i] == 'desc':
                 crit_attrs.append((attr < marker_values[i]))
             elif sort_dirs[i] == 'asc':
diff --git a/cinder/tests/unit/api/v3/test_volumes.py b/cinder/tests/unit/api/v3/test_volumes.py
index 52d5f3fc9f0..72045da7c11 100644
--- a/cinder/tests/unit/api/v3/test_volumes.py
+++ b/cinder/tests/unit/api/v3/test_volumes.py
@@ -192,14 +192,17 @@ class VolumeApiTest(test.TestCase):
         # Create volumes in project 1
         db.volume_create(self.ctxt, {'display_name': 'test1',
                                      'project_id': fake.PROJECT_ID,
-                                     'volume_type_id': fake.VOLUME_TYPE_ID})
+                                     'volume_type_id': fake.VOLUME_TYPE_ID,
+                                     'id': fake.VOLUME_ID})
         db.volume_create(self.ctxt, {'display_name': 'test2',
                                      'project_id': fake.PROJECT_ID,
-                                     'volume_type_id': fake.VOLUME_TYPE_ID})
+                                     'volume_type_id': fake.VOLUME_TYPE_ID,
+                                     'id': fake.VOLUME2_ID})
         # Create volume in project 2
         db.volume_create(self.ctxt, {'display_name': 'test3',
                                      'project_id': fake.PROJECT2_ID,
-                                     'volume_type_id': fake.VOLUME_TYPE_ID})
+                                     'volume_type_id': fake.VOLUME_TYPE_ID,
+                                     'id': fake.VOLUME3_ID})
 
     def test_volume_index_filter_by_glance_metadata(self):
         vols = self._create_volume_with_glance_metadata()
@@ -332,6 +335,22 @@ class VolumeApiTest(test.TestCase):
         self.assertEqual(1, len(res_dict['volumes']))
         self.assertEqual(metadata, res_dict['volumes'][0]['metadata'])
 
+    def test_list_volume_with_filter_and_paginate(self):
+        self._create_multiple_volumes_with_different_project()
+        test_utils.create_volume(self.ctxt)
+
+        self.mock_object(ViewBuilder, '_get_volume_type',
+                         v2_fakes.fake_volume_type_name_get)
+
+        req = fakes.HTTPRequest.blank(
+            "/v3/volumes/detail?all_tenants=1"
+            "&sort=bootable:asc&with_count=True&limit=5"
+            "&marker=" + fake.VOLUME_ID)
+        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
+        req.environ['cinder.context'] = ctxt
+        res_dict = self.controller._get_volumes(req, is_detail=True)
+        self.assertEqual(2, len(res_dict['volumes']))
+
     def test_volume_index_filter_by_group_id_in_unsupport_version(self):
         self._create_volume_with_group()
         req = fakes.HTTPRequest.blank(("/v3/volumes?group_id=%s") %
diff --git a/releasenotes/notes/bug-2027532-volume-list-sort-by-boolean-fix-49972c69007d5ebc.yaml b/releasenotes/notes/bug-2027532-volume-list-sort-by-boolean-fix-49972c69007d5ebc.yaml
new file mode 100644
index 00000000000..a5cba19abfc
--- /dev/null
+++ b/releasenotes/notes/bug-2027532-volume-list-sort-by-boolean-fix-49972c69007d5ebc.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    `Bug #2027532 <https://bugs.launchpad.net/cinder/+bug/2027532>`_:
+    Fixed Cinder API HTTP 500 when issuing a volume list and sorting by a
+    boolean field (i.e. "bootable").