]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix _get_rbd_image_refs caching 47119/head
authorPere Diaz Bou <pdiazbou@redhat.com>
Fri, 15 Jul 2022 14:30:15 +0000 (16:30 +0200)
committerPere Diaz Bou <pdiazbou@redhat.com>
Wed, 10 Aug 2022 15:41:49 +0000 (17:41 +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>
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 2d2a8b5e95a5c41cb5a68fcde347b4e1a717a861..d7dd7e5c703ffbbf4b8a69e13fe82200a351cea2 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 eab5f65df5262db372065c40d6dd662ed05f8a5a..4f8ca7133036707b8bc905566ad45509c89f86db 100644 (file)
@@ -101,6 +101,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;
@@ -630,7 +632,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 5a71d7db40bbbec5bb3a0e8f6eb52065719cfcc7..d2a0312029b343d96526dd78c6191089bf7c4dcc 100644 (file)
@@ -241,6 +241,7 @@ class RbdConfiguration(object):
 
 
 class RbdService(object):
+    _rbd_inst = rbd.RBD()
 
     @classmethod
     def _rbd_disk_usage(cls, image, snaps, whole_object=True):
@@ -390,9 +391,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):
@@ -400,8 +424,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':
@@ -417,22 +440,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
@@ -468,20 +482,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 3e82ceda624281b03d1e94cf476f29352f422fe5..afd316c41cbbc9cf8a54d64ca40cb51a349cad24 100644 (file)
@@ -23,6 +23,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')
@@ -54,11 +59,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',
@@ -82,10 +85,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'
@@ -99,21 +100,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'))
@@ -158,3 +159,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])