]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Rbd Mirroring improvements 49499/head
authorAashish Sharma <aasharma@redhat.com>
Wed, 7 Dec 2022 05:25:24 +0000 (10:55 +0530)
committerAashish Sharma <aasharma@redhat.com>
Mon, 19 Dec 2022 14:17:41 +0000 (19:47 +0530)
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 <aasharma@redhat.com>
(cherry picked from commit 1a37c5e001e0bbc2720e6dfabf6ba04442acabb7)
(cherry picked from commit a85ed9412faa3b3389cf2bdca7d11142bd571ddf)

 Conflicts:
src/pybind/mgr/dashboard/services/rbd.py

12 files changed:
src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd-mirroring.service.ts
src/pybind/mgr/dashboard/services/rbd.py

index 7251473773dfcd26eeb107f470e3156ef31cd4b7..40c7fef992bbade3ddd8b87e2569d56526e18297 100644 (file)
@@ -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)
index 3ee1fa81334285d4ee56b38533e4bf9662bf5d9e..8000751df5f671270d8bea6dd9fb770bd03bd4e9 100644 (file)
@@ -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);
       })
     );
index 8614dfe72bccbd50973ceca123652e908760aee4..470a5f7b11522bc2af6e48cfef3f815f60e08b72 100644 (file)
         <cd-rbd-snapshot-list [snapshots]="selection.snapshots"
                               [featuresName]="selection.features_name"
                               [poolName]="selection.pool_name"
+                              [primary]="selection.primary"
                               [namespace]="selection.namespace"
                               [mirroring]="selection.mirror_mode"
                               [rbdName]="selection.name"></cd-rbd-snapshot-list>
index 38f20476207908f961593bc7a98fe47b2250c934..fed1956a6033020f8ab68c8163b6a1b2f01c8473 100644 (file)
           <label class="cd-col-form-label"
                  i18n>Schedule Interval
           <cd-helper i18n-html
-                     html="Create Mirror-Snapshots automatically on a periodic basis. The interval can be specified in days, hours, or minutes using d, h, m suffix respectively.">
+                     html="Create Mirror-Snapshots automatically on a periodic basis. The interval can be specified in days, hours, or minutes using d, h, m suffix respectively. To create mirror snapshots, you must import or create and have available peers to mirror">
           </cd-helper></label>
           <div class="cd-col-form-input">
             <input id="schedule"
                    formControlName="schedule"
                    i18n-placeholder
                    placeholder="e.g., 12h or 1d or 10m"
-                   [attr.disabled]="(mode === rbdFormMode.editing) ? true : null">
+                   [attr.disabled]="(peerConfigured === false) ? true : null">
           </div>
         </div>
 
index 7f7815c00ac922591efbd291714f9878ee3cd4a4..7bf543f1af729b12f721ef6f5779a00dd3a86265 100644 (file)
@@ -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');
index f6c6af57947a8aa7572914372579a582300b4dd3..b9e3add709cf8f3bf35db988e5b88b03ce5a365c 100644 (file)
@@ -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');
     }
index 712d771c5d4e00b212de525b740bd6a08ad99091..7ab53219ad560f60d6c9d07ee0fe862c81a2aaa3 100644 (file)
@@ -65,8 +65,7 @@
   <span *ngIf="value.length === 3; else probb"
         class="badge badge-info">{{ value[0] }}</span>&nbsp;
   <span *ngIf="value.length === 3"
-        class="badge badge-info"
-        [ngbTooltip]="'Next scheduled snapshot on' + ' ' + (value[2] | cdDate)">{{ value[1] }}</span>
+        class="badge badge-info">{{ value[1] }}</span>&nbsp;
   <span *ngIf="row.primary === true"
         class="badge badge-info"
         i18n>primary</span>
   </ng-template>
 </ng-template>
 
+<ng-template #scheduleTpl
+             let-value="value"
+             let-row="row">
+  <span *ngIf="value.length === 3"
+        class="badge badge-info">{{ value[2] | cdDate  }}</span>
+</ng-template>
+
 <ng-template #flattenTpl
              let-value>
   You are about to flatten
index a24e59f8203d10627b4c76923d92438d7252db8b..79fad415e9e4b2b8079cf6727bec683779aa2b2a 100644 (file)
@@ -53,6 +53,8 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
   parentTpl: TemplateRef<any>;
   @ViewChild('nameTpl')
   nameTpl: TemplateRef<any>;
+  @ViewChild('scheduleTpl', { static: true })
+  scheduleTpl: TemplateRef<any>;
   @ViewChild('mirroringTpl', { static: true })
   mirroringTpl: TemplateRef<any>;
   @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
       }
     ];
 
index ca72007ee81e5b973d5b6dd852318b488d895dc0..c4cd791e595967c18cc331f69a167877ea307aef 100644 (file)
@@ -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,
index df66b0e8842ae01dd96e61d657e635cbbc8626c4..da09add65a31abd09ef1b208a3065e9c03e518af 100644 (file)
@@ -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<any>;
@@ -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();
index 4958382e27982a1a1f06aeaafbbe6a1775e28e78..9dc574e4875d44d13d5aeadf5f314fe2d8fa3fdf 100644 (file)
@@ -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'
index 659940d74980a8521749fbe10c24b0dfe9754c54..9186100adb1d707e69d9e63f3aa02adcf82547da 100644 (file)
@@ -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():