From ad3b4d3c30009d8bd5bcfa97d5607a1dd759d79b Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Wed, 7 Dec 2022 10:55:24 +0530 Subject: [PATCH] mgr/dashboard: Rbd Mirroring improvements 1.RBD images in dashboard shows default mirroring as journal 2.snapshot based mirroring schedule Interval got disabled to edit 3.unable to create snapshot of an image using dashboard 4.provide snapshot schedule info in a new column 5.dashboard doesn't allow importing peer bootstrap key to be imported for subsequent pools Fixes: https://tracker.ceph.com/issues/58297 Signed-off-by: Aashish Sharma (cherry picked from commit 1a37c5e001e0bbc2720e6dfabf6ba04442acabb7) (cherry picked from commit a85ed9412faa3b3389cf2bdca7d11142bd571ddf) Conflicts: src/pybind/mgr/dashboard/services/rbd.py --- src/pybind/mgr/dashboard/controllers/rbd.py | 20 +++++++++--------- .../mirroring/overview/overview.component.ts | 3 +-- .../rbd-details/rbd-details.component.html | 1 + .../block/rbd-form/rbd-form.component.html | 4 ++-- .../block/rbd-form/rbd-form.component.spec.ts | 5 +++++ .../ceph/block/rbd-form/rbd-form.component.ts | 21 +++++++++++++------ .../block/rbd-list/rbd-list.component.html | 10 +++++++-- .../ceph/block/rbd-list/rbd-list.component.ts | 9 ++++++++ .../rbd-snapshot-list.component.spec.ts | 4 ++++ .../rbd-snapshot-list.component.ts | 14 +++++++++++++ .../app/shared/api/rbd-mirroring.service.ts | 4 ++++ src/pybind/mgr/dashboard/services/rbd.py | 5 +++-- 12 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rbd.py b/src/pybind/mgr/dashboard/controllers/rbd.py index 7251473773dfc..40c7fef992bba 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd.py +++ b/src/pybind/mgr/dashboard/controllers/rbd.py @@ -185,6 +185,16 @@ class Rbd(RESTController): if size and size != image.size(): image.resize(size) + mirror_image_info = image.mirror_image_get_info() + if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED: + RbdMirroringService.enable_image( + image_name, pool_name, namespace, + MIRROR_IMAGE_MODE[mirror_mode]) + elif (enable_mirror is False + and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED): + RbdMirroringService.disable_image( + image_name, pool_name, namespace) + # check enable/disable features if features is not None: curr_features = format_bitmask(image.features()) @@ -208,16 +218,6 @@ class Rbd(RESTController): RbdConfiguration(pool_ioctx=ioctx, image_name=image_name).set_configuration( configuration) - mirror_image_info = image.mirror_image_get_info() - if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED: - RbdMirroringService.enable_image( - image_name, pool_name, namespace, - MIRROR_IMAGE_MODE[mirror_mode]) - elif (enable_mirror is False - and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED): - RbdMirroringService.disable_image( - image_name, pool_name, namespace) - if primary and not mirror_image_info['primary']: RbdMirroringService.promote_image( image_name, pool_name, namespace) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts index 3ee1fa8133428..8000751df5f67 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts @@ -59,7 +59,7 @@ export class OverviewComponent implements OnInit, OnDestroy { icon: Icons.download, click: () => this.importBootstrapModal(), name: $localize`Import Bootstrap Token`, - disable: () => this.peersExist + disable: () => false }; this.tableActions = [createBootstrapAction, importBootstrapAction]; } @@ -70,7 +70,6 @@ export class OverviewComponent implements OnInit, OnDestroy { this.subs.add( this.rbdMirroringService.subscribeSummary((data) => { this.status = data.content_data.status; - this.peersExist = !!data.content_data.pools.find((o: Pool) => o['peer_uuids'].length > 0); }) ); 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 8614dfe72bccb..470a5f7b11522 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 @@ -132,6 +132,7 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html index 38f2047620790..fed1956a60330 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html @@ -281,7 +281,7 @@
+ [attr.disabled]="(peerConfigured === false) ? true : null">
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts index 7f7815c00ac92..7bf543f1af729 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts @@ -452,6 +452,9 @@ describe('RbdFormComponent', () => { it('should set and disable exclusive-lock only for the journal mode', () => { component.poolMirrorMode = 'pool'; + component.mirroring = true; + const journal = fixture.debugElement.query(By.css('#journal')).nativeElement; + journal.click(); fixture.detectChanges(); const exclusiveLocks = fixture.debugElement.query(By.css('#exclusive-lock')).nativeElement; expect(exclusiveLocks.checked).toBe(true); @@ -462,6 +465,7 @@ describe('RbdFormComponent', () => { component.mirroring = true; fixture.detectChanges(); const journal = fixture.debugElement.query(By.css('#journal')).nativeElement; + journal.click(); expect(journal.checked).toBe(true); const request = component.createRequest(); expect(request.features).toContain('journaling'); @@ -471,6 +475,7 @@ describe('RbdFormComponent', () => { component.mirroring = true; fixture.detectChanges(); const journal = fixture.debugElement.query(By.css('#journal')).nativeElement; + journal.click(); expect(journal.checked).toBe(true); const request = component.editRequest(); expect(request.features).toContain('journaling'); 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 f6c6af57947a8..b9e3add709cf8 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 @@ -67,6 +67,7 @@ export class RbdFormComponent extends CdForm implements OnInit { }>(1); pool: string; + peerConfigured = false; advancedEnabled = false; @@ -193,11 +194,11 @@ export class RbdFormComponent extends CdForm implements OnInit { return acc; }, {}) ), - mirroring: new FormControl(false), + mirroring: new FormControl(''), schedule: new FormControl('', { validators: [Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/)] // check schedule interval to be in format - 1d or 1h or 1m }), - mirroringMode: new FormControl(this.mirroringOptions[0]), + mirroringMode: new FormControl(''), stripingUnit: new FormControl(this.defaultStripingUnit), stripingCount: new FormControl(this.defaultStripingCount, { updateOn: 'blur' @@ -257,6 +258,16 @@ export class RbdFormComponent extends CdForm implements OnInit { setMirrorMode() { this.mirroring = !this.mirroring; this.setExclusiveLock(); + this.checkPeersConfigured(); + } + + checkPeersConfigured(poolname?: string) { + const poolName = poolname ? poolname : this.rbdForm.get('pool').value; + this.rbdMirroringService.getPeerForPool(poolName).subscribe((resp: any) => { + if (resp.length > 0) { + this.peerConfigured = true; + } + }); } setPoolMirrorMode() { @@ -274,10 +285,6 @@ export class RbdFormComponent extends CdForm implements OnInit { this.mirroring = false; this.rbdForm.get('mirroring').setValue(this.mirroring); this.rbdForm.get('mirroring').disable(); - } else if (this.mode !== this.rbdFormMode.editing) { - this.rbdForm.get('mirroring').enable(); - this.mirroring = true; - this.rbdForm.get('mirroring').setValue(this.mirroring); } }); } @@ -319,6 +326,7 @@ export class RbdFormComponent extends CdForm implements OnInit { this.snapName = decodeURIComponent(params.snap); } promises['rbd'] = this.rbdService.get(imageSpec); + this.checkPeersConfigured(imageSpec.poolName); }); } else { // New image @@ -636,6 +644,7 @@ export class RbdFormComponent extends CdForm implements OnInit { request.name = this.rbdForm.getValue('name'); request.schedule_interval = this.rbdForm.getValue('schedule'); request.size = this.formatter.toBytes(this.rbdForm.getValue('size')); + if (this.poolMirrorMode === 'image') { request.mirror_mode = this.rbdForm.getValue('mirroringMode'); } 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 712d771c5d4e0..7ab53219ad560 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 @@ -65,8 +65,7 @@ {{ value[0] }}  {{ value[1] }} + class="badge badge-info">{{ value[1] }}  primary @@ -78,6 +77,13 @@ + + {{ value[2] | cdDate }} + + You are about to flatten 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 a24e59f8203d1..79fad415e9e4b 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 @@ -53,6 +53,8 @@ export class RbdListComponent extends ListWithDetails implements OnInit { parentTpl: TemplateRef; @ViewChild('nameTpl') nameTpl: TemplateRef; + @ViewChild('scheduleTpl', { static: true }) + scheduleTpl: TemplateRef; @ViewChild('mirroringTpl', { static: true }) mirroringTpl: TemplateRef; @ViewChild('flattenTpl', { static: true }) @@ -305,6 +307,13 @@ export class RbdListComponent extends ListWithDetails implements OnInit { flexGrow: 3, sortable: false, cellTemplate: this.mirroringTpl + }, + { + name: $localize`Next Scheduled Snapshot`, + prop: 'mirror_mode', + flexGrow: 3, + sortable: false, + cellTemplate: this.scheduleTpl } ]; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts index ca72007ee81e5..c4cd791e59596 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts @@ -8,6 +8,7 @@ import { MockComponent } from 'ng-mocks'; import { ToastrModule } from 'ngx-toastr'; import { Subject, throwError as observableThrowError } from 'rxjs'; +import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service'; import { RbdService } from '~/app/shared/api/rbd.service'; import { ComponentsModule } from '~/app/shared/components/components.module'; import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; @@ -85,6 +86,7 @@ describe('RbdSnapshotListComponent', () => { describe('api delete request', () => { let called: boolean; let rbdService: RbdService; + let rbdMirroringService: RbdMirroringService; let notificationService: NotificationService; let authStorageService: AuthStorageService; @@ -93,6 +95,7 @@ describe('RbdSnapshotListComponent', () => { const modalService = TestBed.inject(ModalService); const actionLabelsI18n = TestBed.inject(ActionLabelsI18n); called = false; + rbdMirroringService = new RbdMirroringService(null, null); rbdService = new RbdService(null, null); notificationService = new NotificationService(null, null, null); authStorageService = new AuthStorageService(); @@ -103,6 +106,7 @@ describe('RbdSnapshotListComponent', () => { null, null, rbdService, + rbdMirroringService, null, notificationService, null, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts index df66b0e8842ae..da09add65a31a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts @@ -13,6 +13,7 @@ import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import moment from 'moment'; import { of } from 'rxjs'; +import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service'; import { RbdService } from '~/app/shared/api/rbd.service'; import { CdHelperClass } from '~/app/shared/classes/cd-helper.class'; import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component'; @@ -58,6 +59,8 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges { @Input() mirroring: string; @Input() + primary: boolean; + @Input() rbdName: string; @ViewChild('nameTpl') nameTpl: TemplateRef; @@ -76,6 +79,8 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges { modalRef: NgbModalRef; + peerConfigured = false; + builders = { 'rbd/snap/create': (metadata: any) => { const model = new RbdSnapshotModel(); @@ -90,6 +95,7 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges { private dimlessBinaryPipe: DimlessBinaryPipe, private cdDatePipe: CdDatePipe, private rbdService: RbdService, + private rbdMirrorService: RbdMirroringService, private taskManagerService: TaskManagerService, private notificationService: NotificationService, private summaryService: SummaryService, @@ -142,12 +148,20 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges { } ]; + this.rbdMirrorService.getPeerForPool(this.poolName).subscribe((resp: any) => { + if (resp.length > 0) { + this.peerConfigured = true; + } + }); + this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName); this.rbdTableActions = new RbdSnapshotActionsModel( this.actionLabels, this.featuresName, this.rbdService ); + this.rbdTableActions.create.disable = () => + !this.primary || (!this.peerConfigured && this.mirroring === 'snapshot'); this.rbdTableActions.create.click = () => this.openCreateSnapshotModal(); this.rbdTableActions.rename.click = () => this.openEditSnapshotModal(); this.rbdTableActions.protect.click = () => this.toggleProtection(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts index 4958382e27982..9dc574e4875d4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts @@ -94,6 +94,10 @@ export class RbdMirroringService { return this.http.get(`api/block/mirroring/pool/${poolName}/peer/${peerUUID}`); } + getPeerForPool(poolName: string) { + return this.http.get(`api/block/mirroring/pool/${poolName}/peer`); + } + addPeer(poolName: string, request: any) { return this.http.post(`api/block/mirroring/pool/${poolName}/peer`, request, { observe: 'response' diff --git a/src/pybind/mgr/dashboard/services/rbd.py b/src/pybind/mgr/dashboard/services/rbd.py index 659940d74980a..9186100adb1d7 100644 --- a/src/pybind/mgr/dashboard/services/rbd.py +++ b/src/pybind/mgr/dashboard/services/rbd.py @@ -272,8 +272,9 @@ class RbdService(object): def _rbd_image(cls, ioctx, pool_name, namespace, image_name): # pylint: disable=R0912 with rbd.Image(ioctx, image_name) as img: stat = img.stat() + mirror_info = img.mirror_image_get_info() mirror_mode = img.mirror_image_get_mode() - if mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_JOURNAL: + if mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_JOURNAL and mirror_info['state'] != rbd.RBD_MIRROR_IMAGE_DISABLED: # noqa E501 #pylint: disable=line-too-long stat['mirror_mode'] = 'journal' elif mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT: stat['mirror_mode'] = 'snapshot' @@ -283,7 +284,7 @@ class RbdService(object): if scheduled_image['image'] == get_image_spec(pool_name, namespace, image_name): stat['schedule_info'] = scheduled_image else: - stat['mirror_mode'] = 'unknown' + stat['mirror_mode'] = 'Disabled' stat['name'] = image_name if img.old_format(): -- 2.39.5