]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix _get_rbd_image_refs caching 47636/head
authorPere Diaz Bou <pdiazbou@redhat.com>
Fri, 15 Jul 2022 14:30:15 +0000 (16:30 +0200)
committerPere Diaz Bou <pdiazbou@redhat.com>
Tue, 16 Aug 2022 14:35:19 +0000 (16:35 +0200)
We were cahing _get_rbd_image_refs by Ioctx but the namespace to which
a ioctx is set is also relevant information regarding the references
of a pool.

Signed-off-by: Pere Diaz Bou <pdiazbou@redhat.com>
(cherry picked from commit 950e66b1207f50e3bc91382e07540181d5059511)

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/services/rbd.py
src/pybind/mgr/dashboard/tests/test_rbd_service.py

index 004ff22c3a4e24290a6aeed129a380691f0747ea..712d771c5d4e00b212de525b740bd6a08ad99091 100644 (file)
@@ -12,6 +12,7 @@
           selectionType="single"
           [hasDetails]="true"
           [status]="tableStatus"
+          [maxLimit]="25"
           [autoReload]="-1"
           (fetchData)="taskListService.fetch($event)"
           (setExpandedRow)="setExpandedRow($event)"
index f4334c44e779c30769fa270ecf7ce898f4ae6091..6212c95c8a02cdc295cdc6143d8a9cb58fdd6c34 100644 (file)
           <span *ngIf="selectionType">
             {{ selectedCount }} <ng-container i18n="X selected">selected</ng-container> /
           </span>
-          <span *ngIf="rowCount != data?.length">
-            {{ rowCount }} <ng-container i18n="X found">found</ng-container> /
-          </span>
-          <span>
+
+          <!-- rowCount might have different semantics with or without serverSide.
+            We treat serverSide (backend-driven tables) as a specific case.
+          -->
+          <span *ngIf="!serverSide else serverSideTpl">
+            <span *ngIf="rowCount != data?.length">
+              {{ rowCount }} <ng-container i18n="X found">found</ng-container> /
+            </span>
             {{ data?.length || 0 }} <ng-container i18n="X total">total</ng-container>
           </span>
+
+          <ng-template #serverSideTpl>
+            {{ data?.length || 0 }} <ng-container i18n="X found">found</ng-container> /
+            {{ rowCount }} <ng-container i18n="X total">total</ng-container>
+          </ng-template>
         </div>
         <datatable-pager [pagerLeftArrowIcon]="paginationClasses.pagerPrevious"
                          [pagerRightArrowIcon]="paginationClasses.pagerNext"
index 927b1b13549ad29623211121dc0af2f88293c333..6a37468c272222b9139134ff01c55a146c70cab1 100644 (file)
@@ -99,6 +99,8 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   // Page size to show. Set to 0 to show unlimited number of rows.
   @Input()
   limit? = 10;
+  @Input()
+  maxLimit? = 9999;
   // Has the row details?
   @Input()
   hasDetails = false;
@@ -627,7 +629,13 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   setLimit(e: any) {
     const value = Number(e.target.value);
     if (value > 0) {
-      this.userConfig.limit = value;
+      if (this.maxLimit && value > this.maxLimit) {
+        this.userConfig.limit = this.maxLimit;
+        // change input field to maxLimit
+        e.srcElement.value = this.maxLimit;
+      } else {
+        this.userConfig.limit = value;
+      }
     }
     if (this.serverSide) {
       this.reloadData();
index 0c41bb83a17a95a2334abefa89b40f51e1826330..f14aa244f8e60b7b9b33ba1b8f49664d5b3c5545 100644 (file)
@@ -262,6 +262,7 @@ class RbdConfiguration(object):
 
 
 class RbdService(object):
+    _rbd_inst = rbd.RBD()
 
     @classmethod
     def _rbd_disk_usage(cls, image, snaps, whole_object=True):
@@ -399,9 +400,32 @@ class RbdService(object):
 
     @classmethod
     @ttl_cache(10)
-    def _rbd_image_refs(cls, ioctx):
-        rbd_inst = rbd.RBD()
-        return rbd_inst.list2(ioctx)
+    def get_ioctx(cls, pool_name, namespace=''):
+        ioctx = mgr.rados.open_ioctx(pool_name)
+        ioctx.set_namespace(namespace)
+        return ioctx
+
+    @classmethod
+    @ttl_cache(30)
+    def _rbd_image_refs(cls, pool_name, namespace=''):
+        # We add and set the namespace here so that we cache by ioctx and namespace.
+        images = []
+        ioctx = cls.get_ioctx(pool_name, namespace)
+        images = cls._rbd_inst.list2(ioctx)
+        return images
+
+    @classmethod
+    @ttl_cache(30)
+    def _pool_namespaces(cls, pool_name, namespace=None):
+        namespaces = []
+        if namespace:
+            namespaces = [namespace]
+        else:
+            ioctx = cls.get_ioctx(pool_name, namespace=rados.LIBRADOS_ALL_NSPACES)
+            namespaces = cls._rbd_inst.namespace_list(ioctx)
+            # images without namespace
+            namespaces.append('')
+        return namespaces
 
     @classmethod
     def _rbd_image_stat(cls, ioctx, pool_name, namespace, image_name):
@@ -409,8 +433,7 @@ class RbdService(object):
 
     @classmethod
     def _rbd_image_stat_removing(cls, ioctx, pool_name, namespace, image_id):
-        rbd_inst = rbd.RBD()
-        img = rbd_inst.trash_get(ioctx, image_id)
+        img = cls._rbd_inst.trash_get(ioctx, image_id)
         img_spec = get_image_spec(pool_name, namespace, image_id)
 
         if img['source'] == 'REMOVING':
@@ -426,22 +449,13 @@ class RbdService(object):
     @classmethod
     def _rbd_pool_image_refs(cls, pool_names: List[str], namespace: Optional[str] = None):
         joint_refs = []
-        rbd_inst = rbd.RBD()
         for pool in pool_names:
-            with mgr.rados.open_ioctx(pool) as ioctx:
-                if namespace:
-                    namespaces = [namespace]
-                else:
-                    namespaces = rbd_inst.namespace_list(ioctx)
-                    # images without namespace
-                    namespaces.append('')
-                for current_namespace in namespaces:
-                    ioctx.set_namespace(current_namespace)
-                    image_refs = cls._rbd_image_refs(ioctx)
-                    for image in image_refs:
-                        image['namespace'] = current_namespace
-                        image['pool_name'] = pool
-                        joint_refs.append(image)
+            for current_namespace in cls._pool_namespaces(pool, namespace=namespace):
+                image_refs = cls._rbd_image_refs(pool, current_namespace)
+                for image in image_refs:
+                    image['namespace'] = current_namespace
+                    image['pool_name'] = pool
+                    joint_refs.append(image)
         return joint_refs
 
     @classmethod
@@ -477,20 +491,19 @@ class RbdService(object):
             end = len(image_refs)
         for image_ref in sorted(image_refs, key=lambda v: v[sort_by],
                                 reverse=descending)[offset:end]:
-            with mgr.rados.open_ioctx(image_ref['pool_name']) as ioctx:
-                ioctx.set_namespace(image_ref['namespace'])
+            ioctx = cls.get_ioctx(image_ref['pool_name'], namespace=image_ref['namespace'])
+            try:
+                stat = cls._rbd_image_stat(
+                    ioctx, image_ref['pool_name'], image_ref['namespace'], image_ref['name'])
+            except rbd.ImageNotFound:
+                # Check if the RBD has been deleted partially. This happens for example if
+                # the deletion process of the RBD has been started and was interrupted.
                 try:
-                    stat = cls._rbd_image_stat(
-                        ioctx, image_ref['pool_name'], image_ref['namespace'], image_ref['name'])
+                    stat = cls._rbd_image_stat_removing(
+                        ioctx, image_ref['pool_name'], image_ref['namespace'], image_ref['id'])
                 except rbd.ImageNotFound:
-                    # Check if the RBD has been deleted partially. This happens for example if
-                    # the deletion process of the RBD has been started and was interrupted.
-                    try:
-                        stat = cls._rbd_image_stat_removing(
-                            ioctx, image_ref['pool_name'], image_ref['namespace'], image_ref['id'])
-                    except rbd.ImageNotFound:
-                        continue
-                result.append(stat)
+                    continue
+            result.append(stat)
         return result, len(image_refs)
 
     @classmethod
index f5001bccf86d7933fb643c19c0cdcfed4c99e898..c43be93c9439924b23bdf0327fad49b02ac474cf 100644 (file)
@@ -24,6 +24,11 @@ class ImageNotFoundStub(Exception):
 
 class RbdServiceTest(unittest.TestCase):
 
+    def setUp(self):
+        # pylint: disable=protected-access
+        RbdService._rbd_inst = mock.Mock()
+        self.rbd_inst_mock = RbdService._rbd_inst
+
     def test_compose_image_spec(self):
         self.assertEqual(get_image_spec('mypool', 'myns', 'myimage'), 'mypool/myns/myimage')
         self.assertEqual(get_image_spec('mypool', None, 'myimage'), 'mypool/myimage')
@@ -55,11 +60,9 @@ class RbdServiceTest(unittest.TestCase):
         config = RbdConfiguration('good-pool')
         self.assertEqual(config.list(), [1, 2, 3])
 
-    @mock.patch('dashboard.services.rbd.rbd.RBD')
-    def test_rbd_image_stat_removing(self, rbd_mock):
+    def test_rbd_image_stat_removing(self):
         time = datetime.utcnow()
-        rbd_inst_mock = rbd_mock.return_value
-        rbd_inst_mock.trash_get.return_value = {
+        self.rbd_inst_mock.trash_get.return_value = {
             'id': '3c1a5ee60a88',
             'name': 'test_rbd',
             'source': 'REMOVING',
@@ -83,10 +86,8 @@ class RbdServiceTest(unittest.TestCase):
         })
 
     @mock.patch('dashboard.services.rbd.rbd.ImageNotFound', new_callable=lambda: ImageNotFoundStub)
-    @mock.patch('dashboard.services.rbd.rbd.RBD')
-    def test_rbd_image_stat_filter_source_user(self, rbd_mock, _):
-        rbd_inst_mock = rbd_mock.return_value
-        rbd_inst_mock.trash_get.return_value = {
+    def test_rbd_image_stat_filter_source_user(self, _):
+        self.rbd_inst_mock.trash_get.return_value = {
             'id': '3c1a5ee60a88',
             'name': 'test_rbd',
             'source': 'USER'
@@ -100,21 +101,21 @@ class RbdServiceTest(unittest.TestCase):
                       str(ctx.exception))
 
     @mock.patch('dashboard.services.rbd.rbd.ImageNotFound', new_callable=lambda: ImageNotFoundStub)
+    @mock.patch('dashboard.services.rbd.RbdService._pool_namespaces')
     @mock.patch('dashboard.services.rbd.RbdService._rbd_image_stat_removing')
     @mock.patch('dashboard.services.rbd.RbdService._rbd_image_stat')
     @mock.patch('dashboard.services.rbd.RbdService._rbd_image_refs')
-    @mock.patch('dashboard.services.rbd.rbd.RBD')
-    def test_rbd_pool_list(self, rbd_mock, rbd_image_ref_mock, rbd_image_stat_mock,
-                           rbd_image_stat_removing_mock, _):
+    def test_rbd_pool_list(self, rbd_image_ref_mock, rbd_image_stat_mock,
+                           rbd_image_stat_removing_mock, pool_namespaces, _):
         time = datetime.utcnow()
 
         ioctx_mock = MagicMock()
         mgr.rados = MagicMock()
         mgr.rados.open_ioctx.return_value = ioctx_mock
 
-        rbd_inst_mock = rbd_mock.return_value
-        rbd_inst_mock.namespace_list.return_value = []
+        self.rbd_inst_mock.namespace_list.return_value = []
         rbd_image_ref_mock.return_value = [{'name': 'test_rbd', 'id': '3c1a5ee60a88'}]
+        pool_namespaces.return_value = ['']
 
         rbd_image_stat_mock.side_effect = mock.Mock(side_effect=ImageNotFoundStub(
             'RBD image not found test_pool/3c1a5ee60a88'))
@@ -159,3 +160,15 @@ class RbdServiceTest(unittest.TestCase):
                     RBDSchedulerInterval(interval)
             else:
                 self.assertEqual(str(RBDSchedulerInterval(interval)), interval)
+
+    def test_rbd_image_refs_cache(self):
+        ioctx_mock = MagicMock()
+        mgr.rados = MagicMock()
+        mgr.rados.open_ioctx.return_value = ioctx_mock
+        images = [{'image': str(i), 'id': str(i)} for i in range(10)]
+        for i in range(5):
+            self.rbd_inst_mock.list2.return_value = images[i*2:(i*2)+2]
+            ioctx_mock = MagicMock()
+            # pylint: disable=protected-access
+            res = RbdService._rbd_image_refs(ioctx_mock, str(i))
+            self.assertEqual(res, images[i*2:(i*2)+2])