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"),
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':
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
@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
@EndpointDoc('Configure RBD Mirroring')
@CreatePermission
def configure(self):
+ from ..controllers.pool import RBDPool # to avoid circular import
+
rbd_pool = RBDPool()
service = Service()
});
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);
});
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();
}
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`;
</i>
</div>
</div>
+ <!-- Mirroring -->
+ <div class="form-group row"
+ *ngIf="data.applications.selected.includes('rbd')">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-checkbox">
+ <input class="custom-control-input"
+ id="rbdMirroring"
+ name="rbdMirroring"
+ type="checkbox"
+ formControlName="rbdMirroring">
+ <label class="custom-control-label"
+ for="rbdMirroring"
+ i18n>Mirroring</label>
+ <cd-help-text>
+ <span i18n>Check this option to enable Pool based mirroring on a Block(RBD) pool.</span>
+ </cd-help-text>
+ </div>
+ </div>
+ </div>
<!-- CRUSH -->
<div *ngIf="isErasure || isReplicated">
compression_max_blob_size: 0,
compression_min_blob_size: 0,
compression_required_ratio: 0,
- pool: 'somePoolName'
+ pool: 'somePoolName',
+ rbd_mirroring: false
},
'pool/edit',
'update'
{
application_metadata: ['ownApp', 'rbd'],
compression_mode: 'unset',
- pool: 'somePoolName'
+ pool: 'somePoolName',
+ rbd_mirroring: false
},
'pool/edit',
'update'
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;
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}`);
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)]
);
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) {
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) {
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
}
private triggerApiTask(pool: Record<string, any>) {
+ 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)
})
type: string
flags:
type: string
+ rbd_mirroring:
+ type: string
type: object
responses:
'200':