selectionType="single"
[hasDetails]="true"
[status]="tableStatus"
+ [maxLimit]="25"
[autoReload]="-1"
(fetchData)="taskListService.fetch($event)"
(setExpandedRow)="setExpandedRow($event)"
<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"
// 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;
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();
class RbdService(object):
+ _rbd_inst = rbd.RBD()
@classmethod
def _rbd_disk_usage(cls, image, snaps, whole_object=True):
@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):
@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':
@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
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
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')
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',
})
@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'
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'))
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])