From 8340d19a67c65c0fc4c07a4daf3535621f295ce0 Mon Sep 17 00:00:00 2001 From: Ernesto Puerta Date: Mon, 11 May 2020 20:33:25 +0200 Subject: [PATCH] mgr/dashboard: work with RBD images v1 Add support for RBD Image Format v1: - This format lacks ID field, required for dashboard. Instead, RBD image `block_name_prefix` is used as unique ID (together with pool id and namespace) - Additionally, `image_format` is now exposed. - In the front-end side: - Copy action on a v1 image will cause the image to be copied to v2 format. - List doesn't allow Move to Trash on v1 images, - Details section now shows `image_format` for images, - Edit Form disables flags not supported for v1 (`deep-flatten`, `layering`, `exclusive-lock`). - Protect does not work on v1 images or v2 images created from v1 ones. Fixes: https://tracker.ceph.com/issues/36354 Signed-off-by: Ernesto Puerta (cherry picked from commit 119851ec89745026c273334efbcbf2d50128c99d) Conflicts: src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html: add row src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts: add imports src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts: add new fields src/pybind/mgr/dashboard/services/ceph_service.py: just add get_pool_name_from_id src/pybind/mgr/dashboard/services/rbd.py: discard changes src/pybind/mgr/dashboard/controllers/rbd.py: bring and adapt code from services/rbd.py Signed-off-by: Ernesto Puerta --- qa/tasks/mgr/dashboard/test_rbd.py | 2 ++ src/pybind/mgr/dashboard/controllers/rbd.py | 10 +++++++++- .../ceph/block/rbd-details/rbd-details.component.html | 5 +++++ .../src/app/ceph/block/rbd-form/rbd-form.component.ts | 10 ++++++++++ .../app/ceph/block/rbd-list/rbd-list.component.html | 2 +- .../src/app/ceph/block/rbd-list/rbd-list.component.ts | 10 ++++++++-- .../frontend/src/app/ceph/block/rbd-list/rbd-model.ts | 7 +++++++ src/pybind/mgr/dashboard/services/ceph_service.py | 3 +-- src/pybind/mgr/dashboard/tests/test_ceph_service.py | 6 ++++-- 9 files changed, 47 insertions(+), 8 deletions(-) diff --git a/qa/tasks/mgr/dashboard/test_rbd.py b/qa/tasks/mgr/dashboard/test_rbd.py index 252ca82df32..68af93d9b9c 100644 --- a/qa/tasks/mgr/dashboard/test_rbd.py +++ b/qa/tasks/mgr/dashboard/test_rbd.py @@ -188,6 +188,8 @@ class RbdTest(DashboardTestCase): 'block_name_prefix': JLeaf(str), 'name': JLeaf(str), 'id': JLeaf(str), + 'unique_id': JLeaf(str), + 'image_format': JLeaf(int), 'pool_name': JLeaf(str), 'features': JLeaf(int), 'features_name': JList(JLeaf(str)), diff --git a/src/pybind/mgr/dashboard/controllers/rbd.py b/src/pybind/mgr/dashboard/controllers/rbd.py index 8555a35ea12..8e0b184c9b7 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd.py +++ b/src/pybind/mgr/dashboard/controllers/rbd.py @@ -105,7 +105,15 @@ class Rbd(RESTController): with rbd.Image(ioctx, image_name) as img: stat = img.stat() stat['name'] = image_name - stat['id'] = img.id() + if img.old_format(): + stat['unique_id'] = '{}/{}'.format(pool_name, stat['block_name_prefix']) + stat['id'] = stat['unique_id'] + stat['image_format'] = 1 + else: + stat['unique_id'] = '{}/{}'.format(pool_name, img.id()) + stat['id'] = img.id() + stat['image_format'] = 2 + stat['pool_name'] = pool_name features = img.features() stat['features'] = features diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html index 889cab1f9a4..2c24eb75ae0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html @@ -112,6 +112,11 @@ class="bold col-sm-1">Order {{ selectedItem.order }} + + Format Version + {{ selection.image_format }} + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts index 6e9bb294ce1..ecd158cb5af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts @@ -21,6 +21,7 @@ import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { FormatterService } from '../../../shared/services/formatter.service'; import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; +import { RBDImageFormat, RbdModel } from '../rbd-list/rbd-model'; import { RbdImageFeature } from './rbd-feature.interface'; import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model'; import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model'; @@ -188,6 +189,15 @@ export class RbdFormComponent implements OnInit { this.rbdForm.get('obj_size').disable(); this.rbdForm.get('stripingUnit').disable(); this.rbdForm.get('stripingCount').disable(); + + /* RBD Image Format v1 */ + this.rbdImage.subscribe((image: RbdModel) => { + if (image.image_format === RBDImageFormat.V1) { + this.rbdForm.get('deep-flatten').disable(); + this.rbdForm.get('layering').disable(); + this.rbdForm.get('exclusive-lock').disable(); + } + }); } disableForClone() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html index ccd95925353..4dd424e23df 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html @@ -6,7 +6,7 @@ [data]="images" columnMode="flex" [columns]="columns" - identifier="id" + identifier="unique_id" [searchableObjects]="true" forceIdentifier="true" selectionType="single" diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts index 2ddaa5ce33b..596941912cd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts @@ -24,7 +24,7 @@ import { TaskWrapperService } from '../../../shared/services/task-wrapper.servic import { URLBuilderService } from '../../../shared/services/url-builder.service'; import { RbdParentModel } from '../rbd-form/rbd-parent.model'; import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component'; -import { RbdModel } from './rbd-model'; +import { RBDImageFormat, RbdModel } from './rbd-model'; const BASE_URL = 'block/rbd'; @@ -71,8 +71,10 @@ export class RbdListComponent implements OnInit { private createRbdFromTask(pool: string, name: string): RbdModel { const model = new RbdModel(); model.id = '-1'; + model.unique_id = '-1'; model.name = name; model.pool_name = pool; + model.image_format = RBDImageFormat.V2; return model; } @@ -134,7 +136,11 @@ export class RbdListComponent implements OnInit { permission: 'delete', icon: 'fa-trash-o', click: () => this.trashRbdModal(), - name: this.actionLabels.TRASH + name: this.actionLabels.TRASH, + disable: (selection: CdTableSelection) => + !selection.first() || + !selection.hasSingleSelection || + selection.first().image_format === RBDImageFormat.V1 }; this.tableActions = [ addAction, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts index 92a77bdd6ea..3920b6ff21a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-model.ts @@ -1,7 +1,14 @@ export class RbdModel { id: string; + unique_id: string; name: string; pool_name: string; + image_format: RBDImageFormat; cdExecuting: string; } + +export enum RBDImageFormat { + V1 = 1, + V2 = 2 +} diff --git a/src/pybind/mgr/dashboard/services/ceph_service.py b/src/pybind/mgr/dashboard/services/ceph_service.py index 9b50c093a8e..6d38fa41079 100644 --- a/src/pybind/mgr/dashboard/services/ceph_service.py +++ b/src/pybind/mgr/dashboard/services/ceph_service.py @@ -118,8 +118,7 @@ class CephService(object): @classmethod def get_pool_name_from_id(cls, pool_id): # type: (int) -> Union[str, None] - pool = cls.get_pool_by_attribute('pool', pool_id) - return pool['pool_name'] if pool is not None else None + return mgr.rados.pool_reverse_lookup(pool_id) @classmethod def get_pool_by_attribute(cls, attribute, value): diff --git a/src/pybind/mgr/dashboard/tests/test_ceph_service.py b/src/pybind/mgr/dashboard/tests/test_ceph_service.py index baf8588117b..5111e68d492 100644 --- a/src/pybind/mgr/dashboard/tests/test_ceph_service.py +++ b/src/pybind/mgr/dashboard/tests/test_ceph_service.py @@ -52,10 +52,12 @@ class CephServiceTest(unittest.TestCase): def test_get_pool_by_attribute_matching_a_not_always_set_attribute(self): self.assertEqual(self.service.get_pool_by_attribute('flaky', 'option_x'), self.pools[1]) - def test_get_pool_name_from_id_with_match(self): + @mock.patch('dashboard.mgr.rados.pool_reverse_lookup', return_value='good_pool') + def test_get_pool_name_from_id_with_match(self, _mock): self.assertEqual(self.service.get_pool_name_from_id(1), 'good_pool') - def test_get_pool_name_from_id_without_match(self): + @mock.patch('dashboard.mgr.rados.pool_reverse_lookup', return_value=None) + def test_get_pool_name_from_id_without_match(self, _mock): self.assertEqual(self.service.get_pool_name_from_id(3), None) def test_get_pool_pg_status(self): -- 2.47.3