]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: support removing OSD in OSDs page
authorKiefer Chang <kiefer.chang@suse.com>
Wed, 11 Dec 2019 03:54:50 +0000 (11:54 +0800)
committerKiefer Chang <kiefer.chang@suse.com>
Wed, 11 Mar 2020 04:08:35 +0000 (12:08 +0800)
Add frontend codes and unit tests.

Fixes: https://tracker.ceph.com/issues/43062
Signed-off-by: Kiefer Chang <kiefer.chang@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts

index f2f0d2fabb3f41cc01616f6544169310357a13be..f939dc7231756c0a205b5009d2b05d7e9550d6dd 100644 (file)
 </ng-template>
 
 <ng-template #criticalConfirmationTpl
-             let-safeToDestroyResult="result"
+             let-safeToPerform="safeToPerform"
+             let-message="message"
              let-actionDescription="actionDescription">
-  <div *ngIf="!safeToDestroyResult['is_safe_to_destroy']"
+  <div *ngIf="!safeToPerform"
        class="danger">
     <cd-alert-panel type="warning"
-                    i18n>The {selection.hasSingleSelection, select, 1 {OSD is} 0 {OSDs are}} not safe to destroy!</cd-alert-panel>
+                    i18n>The {selection.hasSingleSelection, select, 1 {OSD is} 0 {OSDs are}} not safe to be {{ actionDescription }}! {{ message }}</cd-alert-panel>
   </div>
   <ng-container i18n><strong>OSD {{ getSelectedOsdIds() | join }}</strong> will be
 <strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
index 7064628e38dd27317c6a3bc3529bbc564b0b187a..183d67aad59847348a5da27c0f34e38480cef688 100644 (file)
@@ -72,7 +72,13 @@ describe('OsdListComponent', () => {
    */
   const mockSafeToDestroy = () => {
     spyOn(TestBed.get(OsdService), 'safeToDestroy').and.callFake(() =>
-      of({ 'safe-to-destroy': true })
+      of({ is_safe_to_destroy: true })
+    );
+  };
+
+  const mockSafeToDelete = () => {
+    spyOn(TestBed.get(OsdService), 'safeToDelete').and.callFake(() =>
+      of({ is_safe_to_delete: true })
     );
   };
 
@@ -257,7 +263,8 @@ describe('OsdListComponent', () => {
           'Mark Down',
           'Mark Lost',
           'Purge',
-          'Destroy'
+          'Destroy',
+          'Delete'
         ],
         primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' }
       },
@@ -275,7 +282,7 @@ describe('OsdListComponent', () => {
         primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' }
       },
       'create,delete': {
-        actions: ['Create', 'Mark Lost', 'Purge', 'Destroy'],
+        actions: ['Create', 'Mark Lost', 'Purge', 'Destroy', 'Delete'],
         primary: {
           multiple: 'Create',
           executing: 'Mark Lost',
@@ -298,7 +305,8 @@ describe('OsdListComponent', () => {
           'Mark Down',
           'Mark Lost',
           'Purge',
-          'Destroy'
+          'Destroy',
+          'Delete'
         ],
         primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' }
       },
@@ -307,7 +315,7 @@ describe('OsdListComponent', () => {
         primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' }
       },
       delete: {
-        actions: ['Mark Lost', 'Purge', 'Destroy'],
+        actions: ['Mark Lost', 'Purge', 'Destroy', 'Delete'],
         primary: {
           multiple: 'Mark Lost',
           executing: 'Mark Lost',
@@ -387,13 +395,22 @@ describe('OsdListComponent', () => {
       expectOpensModal('Mark Lost', modalClass);
       expectOpensModal('Purge', modalClass);
       expectOpensModal('Destroy', modalClass);
+      mockSafeToDelete();
+      expectOpensModal('Delete', modalClass);
     });
   });
 
   describe('tests if the correct methods are called on confirmation', () => {
     const expectOsdServiceMethodCalled = (
       actionName: string,
-      osdServiceMethodName: 'markOut' | 'markIn' | 'markDown' | 'markLost' | 'purge' | 'destroy'
+      osdServiceMethodName:
+        | 'markOut'
+        | 'markIn'
+        | 'markDown'
+        | 'markLost'
+        | 'purge'
+        | 'destroy'
+        | 'delete'
     ): void => {
       const osdServiceSpy = spyOn(osdService, osdServiceMethodName).and.callFake(() => EMPTY);
       openActionModal(actionName);
@@ -420,6 +437,8 @@ describe('OsdListComponent', () => {
       expectOsdServiceMethodCalled('Mark Lost', 'markLost');
       expectOsdServiceMethodCalled('Purge', 'purge');
       expectOsdServiceMethodCalled('Destroy', 'destroy');
+      mockSafeToDelete();
+      expectOsdServiceMethodCalled('Delete', 'delete');
     });
   });
 });
index 802d6a5e60b46d8ba911281853b3c3b434ea6afb..79c6b8aea9764b14a84b2f31d88f85d7a0941fe4 100644 (file)
@@ -148,6 +148,10 @@ export class OsdListComponent implements OnInit {
             this.i18n('Mark'),
             this.i18n('OSD lost'),
             this.i18n('marked lost'),
+            (ids: number[]) => {
+              return this.osdService.safeToDestroy(JSON.stringify(ids));
+            },
+            'is_safe_to_destroy',
             this.osdService.markLost
           ),
         disable: () => this.isNotSelectedOrInState('up'),
@@ -161,7 +165,11 @@ export class OsdListComponent implements OnInit {
             this.i18n('Purge'),
             this.i18n('OSD'),
             this.i18n('purged'),
-            (id) => {
+            (ids: number[]) => {
+              return this.osdService.safeToDestroy(JSON.stringify(ids));
+            },
+            'is_safe_to_destroy',
+            (id: number) => {
               this.selection = new CdTableSelection();
               return this.osdService.purge(id);
             }
@@ -177,12 +185,36 @@ export class OsdListComponent implements OnInit {
             this.i18n('destroy'),
             this.i18n('OSD'),
             this.i18n('destroyed'),
-            (id) => {
+            (ids: number[]) => {
+              return this.osdService.safeToDestroy(JSON.stringify(ids));
+            },
+            'is_safe_to_destroy',
+            (id: number) => {
               this.selection = new CdTableSelection();
               return this.osdService.destroy(id);
             }
           ),
         disable: () => this.isNotSelectedOrInState('up'),
+        icon: Icons.destroyCircle
+      },
+      {
+        name: this.actionLabels.DELETE,
+        permission: 'delete',
+        click: () =>
+          this.showCriticalConfirmationModal(
+            this.i18n('delete'),
+            this.i18n('OSD'),
+            this.i18n('deleted'),
+            (ids: number[]) => {
+              return this.osdService.safeToDelete(JSON.stringify(ids));
+            },
+            'is_safe_to_delete',
+            (id: number) => {
+              this.selection = new CdTableSelection();
+              return this.osdService.delete(id, true);
+            }
+          ),
+        disable: () => !this.hasOsdSelected,
         icon: Icons.destroy
       }
     ];
@@ -412,20 +444,34 @@ export class OsdListComponent implements OnInit {
     });
   }
 
+  /**
+   * Perform check first and display a critical confirmation modal.
+   * @param {string} actionDescription name of the action.
+   * @param {string} itemDescription the item's name that the action operates on.
+   * @param {string} templateItemDescription the action name to be displayed in modal template.
+   * @param {Function} check the function is called to check if the action is safe.
+   * @param {string} checkKey the safe indicator's key in the check response.
+   * @param {Function} action the action function.
+   * @param {boolean} oneshot if true, action function is called with all items as parameter.
+   *   Otherwise, multiple action functions with individual items are sent.
+   */
   showCriticalConfirmationModal(
     actionDescription: string,
     itemDescription: string,
     templateItemDescription: string,
-    action: (id: number) => Observable<any>
+    check: (ids: number[]) => Observable<any>,
+    checkKey: string,
+    action: (id: number | number[]) => Observable<any>
   ): void {
-    this.osdService.safeToDestroy(JSON.stringify(this.getSelectedOsdIds())).subscribe((result) => {
+    check(this.getSelectedOsdIds()).subscribe((result) => {
       const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
         initialState: {
           actionDescription: actionDescription,
           itemDescription: itemDescription,
           bodyTemplate: this.criticalConfirmationTpl,
           bodyContext: {
-            result: result,
+            safeToPerform: result[checkKey],
+            message: result.message,
             actionDescription: templateItemDescription
           },
           submitAction: () => {
index 02d99fa1f3062d892b4bf4ad6b8fdd216e7c3b70..30cdcd1ea3697e4bbd8015b3b9cdf4f26fcaa56e 100644 (file)
@@ -1,4 +1,4 @@
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 
 import { I18n } from '@ngx-translate/i18n-polyfill';
@@ -138,14 +138,28 @@ export class OsdService {
     return this.http.post(`${this.path}/${id}/destroy`, null);
   }
 
+  delete(id: number, force?: boolean) {
+    const options = force ? { params: new HttpParams().set('force', 'true') } : {};
+    options['observe'] = 'response';
+    return this.http.delete(`${this.path}/${id}`, options);
+  }
+
   safeToDestroy(ids: string) {
     interface SafeToDestroyResponse {
-      'safe-to-destroy': boolean;
+      is_safe_to_destroy: boolean;
       message?: string;
     }
     return this.http.get<SafeToDestroyResponse>(`${this.path}/safe_to_destroy?ids=${ids}`);
   }
 
+  safeToDelete(ids: string) {
+    interface SafeToDeleteResponse {
+      is_safe_to_delete: boolean;
+      message?: string;
+    }
+    return this.http.get<SafeToDeleteResponse>(`${this.path}/safe_to_delete?svc_ids=${ids}`);
+  }
+
   getDevices(osdId: number) {
     return this.http
       .get<CdDevice[]>(`${this.path}/${osdId}/devices`)