From 62f4ddaea0fff8a1d7ee37b49b6f47280fc57ae1 Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Wed, 29 May 2024 10:04:13 +0530 Subject: [PATCH] mgr/dashboard: provide option to enable pool based mirroring mode while creating a pool Fixes: https://tracker.ceph.com/issues/66267 Signed-off-by: Aashish Sharma (cherry picked from commit 2fa7dd0e308a10a968460115033b6bf2408e6e79) --- src/pybind/mgr/dashboard/controllers/pool.py | 18 ++++++++++-- .../dashboard/controllers/rbd_mirroring.py | 6 +++- .../cypress/e2e/pools/pools.e2e-spec.ts | 4 +-- .../frontend/cypress/e2e/pools/pools.po.ts | 3 ++ .../pool/pool-form/pool-form.component.html | 19 +++++++++++++ .../pool-form/pool-form.component.spec.ts | 6 ++-- .../pool/pool-form/pool-form.component.ts | 28 ++++++++++++++++--- src/pybind/mgr/dashboard/openapi.yaml | 2 ++ 8 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/pool.py b/src/pybind/mgr/dashboard/controllers/pool.py index 1e2e04e1b14da..7f9a1300f0f19 100644 --- a/src/pybind/mgr/dashboard/controllers/pool.py +++ b/src/pybind/mgr/dashboard/controllers/pool.py @@ -13,6 +13,7 @@ from ..services.rbd import RbdConfiguration from ..tools import TaskManager, str_to_bool from . import APIDoc, APIRouter, Endpoint, EndpointDoc, ReadPermission, \ RESTController, Task, UIRouter +from .rbd_mirroring import RbdMirroringPoolMode POOL_SCHEMA = ([{ "pool": (int, "pool id"), @@ -159,25 +160,38 @@ class Pool(RESTController): yes_i_really_really_mean_it=True) @pool_task('edit', ['{pool_name}']) - def set(self, pool_name, flags=None, application_metadata=None, configuration=None, **kwargs): + def set(self, pool_name, flags=None, application_metadata=None, configuration=None, + rbd_mirroring=None, **kwargs): self._set_pool_values(pool_name, application_metadata, flags, True, kwargs) if kwargs.get('pool'): pool_name = kwargs['pool'] RbdConfiguration(pool_name).set_configuration(configuration) + if rbd_mirroring is not None: + self._set_mirroring_mode(rbd_mirroring, pool_name) self._wait_for_pgs(pool_name) @pool_task('create', {'pool_name': '{pool}'}) @handle_send_command_error('pool') def create(self, pool, pg_num, pool_type, erasure_code_profile=None, flags=None, - application_metadata=None, rule_name=None, configuration=None, **kwargs): + application_metadata=None, rule_name=None, configuration=None, + rbd_mirroring=None, **kwargs): ecp = erasure_code_profile if erasure_code_profile else None CephService.send_command('mon', 'osd pool create', pool=pool, pg_num=int(pg_num), pgp_num=int(pg_num), pool_type=pool_type, erasure_code_profile=ecp, rule=rule_name) self._set_pool_values(pool, application_metadata, flags, False, kwargs) RbdConfiguration(pool).set_configuration(configuration) + if rbd_mirroring is not None: + self._set_mirroring_mode(rbd_mirroring, pool) self._wait_for_pgs(pool) + def _set_mirroring_mode(self, mirroring_enabled, pool): + rbd_mirroring = RbdMirroringPoolMode() + if str_to_bool(mirroring_enabled): + rbd_mirroring.set_pool_mirror_mode(pool, 'pool') + else: + rbd_mirroring.set_pool_mirror_mode(pool, 'disabled') + def _set_pool_values(self, pool, application_metadata, flags, update_existing, kwargs): current_pool = self._get(pool) if update_existing and kwargs.get('compression_mode') == 'unset': diff --git a/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py b/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py index af30e8415eb79..1e80de74b3b94 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py +++ b/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py @@ -11,7 +11,6 @@ import cherrypy import rbd from .. import mgr -from ..controllers.pool import RBDPool from ..controllers.service import Service from ..security import Scope from ..services.ceph_service import CephService @@ -507,6 +506,9 @@ class RbdMirroringPoolMode(RESTController): @RbdMirroringTask('pool/edit', {'pool_name': '{pool_name}'}, 5.0) def set(self, pool_name, mirror_mode=None): + return self.set_pool_mirror_mode(pool_name, mirror_mode) + + def set_pool_mirror_mode(self, pool_name, mirror_mode): def _edit(ioctx, mirror_mode=None): if mirror_mode: mode_enum = {x[1]: x[0] for x in @@ -674,6 +676,8 @@ class RbdMirroringStatus(BaseController): @EndpointDoc('Configure RBD Mirroring') @CreatePermission def configure(self): + from ..controllers.pool import RBDPool # to avoid circular import + rbd_pool = RBDPool() service = Service() diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts index dd4ab6f3b75a3..536f5fbadc739 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.e2e-spec.ts @@ -28,14 +28,14 @@ describe('Pools page', () => { }); describe('Create, update and destroy', () => { - it('should create a pool', () => { + it('should create a pool with mirroring enabled', () => { pools.existTableCell(poolName, false); pools.navigateTo('create'); pools.create(poolName, 8, 'rbd'); pools.existTableCell(poolName); }); - it('should edit a pools placement group', () => { + it('should edit a pools placement group and check if mirroring is enabled', () => { pools.existTableCell(poolName); pools.edit_pool_pg(poolName, 32); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts index af46355ff1c51..b1c0263dde123 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/pools/pools.po.ts @@ -25,6 +25,7 @@ export class PoolPageHelper extends PageHelper { this.selectOption('pgAutoscaleMode', 'off'); // To show pgNum field cy.get('input[name=pgNum]').clear().type(`${placement_groups}`); this.setApplications(apps); + cy.get('#rbdMirroring').check({ force: true }); cy.get('cd-submit-button').click(); } @@ -32,6 +33,8 @@ export class PoolPageHelper extends PageHelper { this.isPowerOf2(new_pg); this.navigateEdit(name); + cy.get('#rbdMirroring').should('be.checked'); + cy.get('input[name=pgNum]').clear().type(`${new_pg}`); cy.get('cd-submit-button').click(); const str = `${new_pg} active+clean`; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html index 13103da324aab..4dcc8171ccbdb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html @@ -191,6 +191,25 @@ + +
+
+
+ + + + Check this option to enable Pool based mirroring on a Block(RBD) pool. + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts index 7e2bccb32dd2d..5556e4b2df44a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts @@ -1384,7 +1384,8 @@ describe('PoolFormComponent', () => { compression_max_blob_size: 0, compression_min_blob_size: 0, compression_required_ratio: 0, - pool: 'somePoolName' + pool: 'somePoolName', + rbd_mirroring: false }, 'pool/edit', 'update' @@ -1397,7 +1398,8 @@ describe('PoolFormComponent', () => { { application_metadata: ['ownApp', 'rbd'], compression_mode: 'unset', - pool: 'somePoolName' + pool: 'somePoolName', + rbd_mirroring: false }, 'pool/edit', 'update' diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts index c91ca76536725..c46e1b33bd766 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts @@ -37,6 +37,8 @@ import { CrushRuleFormModalComponent } from '../crush-rule-form-modal/crush-rule import { ErasureCodeProfileFormModalComponent } from '../erasure-code-profile-form/erasure-code-profile-form-modal.component'; import { Pool } from '../pool'; import { PoolFormData } from './pool-form-data'; +import { PoolEditModeResponseModel } from '../../block/mirroring/pool-edit-mode-modal/pool-edit-mode-response.model'; +import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service'; interface FormFieldDescription { externalFieldName: string; @@ -97,7 +99,8 @@ export class PoolFormComponent extends CdForm implements OnInit { private taskWrapper: TaskWrapperService, private ecpService: ErasureCodeProfileService, private crushRuleService: CrushRuleService, - public actionLabels: ActionLabelsI18n + public actionLabels: ActionLabelsI18n, + private rbdMirroringService: RbdMirroringService ) { super(); this.editing = this.router.url.startsWith(`/pool/${URLVerbs.EDIT}`); @@ -176,7 +179,8 @@ export class PoolFormComponent extends CdForm implements OnInit { ecOverwrites: new UntypedFormControl(false), compression: compressionForm, max_bytes: new UntypedFormControl(''), - max_objects: new UntypedFormControl(0) + max_objects: new UntypedFormControl(0), + rbdMirroring: new UntypedFormControl(false) }, [CdValidators.custom('form', (): null => null)] ); @@ -284,6 +288,11 @@ export class PoolFormComponent extends CdForm implements OnInit { this.data.pgs = this.form.getValue('pgNum'); this.setAvailableApps(this.data.applications.default.concat(pool.application_metadata)); this.data.applications.selected = pool.application_metadata; + this.rbdMirroringService + .getPool(pool.pool_name) + .subscribe((resp: PoolEditModeResponseModel) => { + this.form.get('rbdMirroring').setValue(resp.mirror_mode === 'pool'); + }); } private setAvailableApps(apps: string[] = this.data.applications.default) { @@ -775,7 +784,14 @@ export class PoolFormComponent extends CdForm implements OnInit { formControlName: 'max_objects', editable: true, resetValue: this.editing ? 0 : undefined - } + }, + this.data.applications.selected.includes('rbd') + ? { externalFieldName: 'rbd_mirroring', formControlName: 'rbdMirroring' } + : { + externalFieldName: 'rbd_mirroring', + formControlName: 'rbdMirroring', + resetValue: undefined + } ]); if (this.info.is_all_bluestore) { @@ -840,6 +856,9 @@ export class PoolFormComponent extends CdForm implements OnInit { const apps = this.data.applications.selected; if (apps.length > 0 || this.editing) { pool['application_metadata'] = apps; + if (apps.includes('rbd')) { + pool['rbd_mirroring'] = this.form.getValue('rbdMirroring'); + } } // Only collect configuration data for replicated pools, as QoS cannot be configured on EC @@ -892,10 +911,11 @@ export class PoolFormComponent extends CdForm implements OnInit { } private triggerApiTask(pool: Record) { + const poolName = pool.hasOwnProperty('srcpool') ? pool.srcpool : pool.pool; this.taskWrapper .wrapTaskAroundCall({ task: new FinishedTask('pool/' + (this.editing ? URLVerbs.EDIT : URLVerbs.CREATE), { - pool_name: pool.hasOwnProperty('srcpool') ? pool.srcpool : pool.pool + pool_name: poolName }), call: this.poolService[this.editing ? URLVerbs.UPDATE : URLVerbs.CREATE](pool) }) diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 806829033d02a..19ebc2405594e 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -9899,6 +9899,8 @@ paths: type: string flags: type: string + rbd_mirroring: + type: string type: object responses: '200': -- 2.39.5