]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Explicitly set the device class of an OSD 32150/head
authorRicardo Marques <rimarques@suse.com>
Tue, 10 Dec 2019 14:16:53 +0000 (14:16 +0000)
committerRicardo Marques <rimarques@suse.com>
Mon, 16 Dec 2019 09:45:58 +0000 (09:45 +0000)
Fixes: https://tracker.ceph.com/issues/43197
Signed-off-by: Ricardo Marques <rimarques@suse.com>
src/pybind/mgr/dashboard/controllers/osd.py
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.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts

index 70fed1d865c6be6c15e1358507d800f848baa5ae..189da4bb63484a45a6d10bf326a8de972426aa52 100644 (file)
@@ -103,6 +103,19 @@ class Osd(RESTController):
             'histogram': histogram,
         }
 
+    def set(self, svc_id, device_class):
+        old_device_class = CephService.send_command('mon', 'osd crush get-device-class',
+                                                    ids=[svc_id])
+        old_device_class = old_device_class[0]['device_class']
+        if old_device_class != device_class:
+            CephService.send_command('mon', 'osd crush rm-device-class',
+                                     ids=[svc_id])
+            if device_class:
+                CephService.send_command('mon', 'osd crush set-device-class', **{
+                    'class': device_class,
+                    'ids': [svc_id]
+                })
+
     @RESTController.Resource('POST', query_params=['deep'])
     @UpdatePermission
     def scrub(self, svc_id, deep=False):
index d3fb7285c67dbb5e22004b9e9916fc00f87f1103..2cd2784b9e8899c634e6032f617f4af4e456fe20 100644 (file)
@@ -7,6 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
 import { TabsModule } from 'ngx-bootstrap/tabs';
+import { ToastrModule } from 'ngx-toastr';
 import { EMPTY, of } from 'rxjs';
 
 import {
@@ -18,6 +19,7 @@ import { CoreModule } from '../../../../core/core.module';
 import { OsdService } from '../../../../shared/api/osd.service';
 import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
 import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { FormModalComponent } from '../../../../shared/components/form-modal/form-modal.component';
 import { TableActionsComponent } from '../../../../shared/datatable/table-actions/table-actions.component';
 import { CdTableAction } from '../../../../shared/models/cd-table-action';
 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
@@ -47,8 +49,8 @@ describe('OsdListComponent', () => {
 
   const setFakeSelection = () => {
     // Default data and selection
-    const selection = [{ id: 1 }];
-    const data = [{ id: 1 }];
+    const selection = [{ id: 1, tree: { device_class: 'ssd' } }];
+    const data = [{ id: 1, tree: { device_class: 'ssd' } }];
 
     // Table data and selection
     component.selection = new CdTableSelection();
@@ -78,6 +80,7 @@ describe('OsdListComponent', () => {
       HttpClientTestingModule,
       PerformanceCounterModule,
       TabsModule.forRoot(),
+      ToastrModule.forRoot(),
       CephModule,
       ReactiveFormsModule,
       RouterTestingModule,
@@ -119,6 +122,9 @@ describe('OsdListComponent', () => {
     const createOsd = (n: number) => ({
       in: 'in',
       up: 'up',
+      tree: {
+        device_class: 'ssd'
+      },
       stats_history: {
         op_out_bytes: [[n, n], [n * 2, n * 2]],
         op_in_bytes: [[n * 3, n * 3], [n * 4, n * 4]]
@@ -239,6 +245,7 @@ describe('OsdListComponent', () => {
       'create,update,delete': {
         actions: [
           'Create',
+          'Edit',
           'Scrub',
           'Deep Scrub',
           'Reweight',
@@ -249,11 +256,20 @@ describe('OsdListComponent', () => {
           'Purge',
           'Destroy'
         ],
-        primary: { multiple: 'Scrub', executing: 'Scrub', single: 'Scrub', no: 'Create' }
+        primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' }
       },
       'create,update': {
-        actions: ['Create', 'Scrub', 'Deep Scrub', 'Reweight', 'Mark Out', 'Mark In', 'Mark Down'],
-        primary: { multiple: 'Scrub', executing: 'Scrub', single: 'Scrub', no: 'Create' }
+        actions: [
+          'Create',
+          'Edit',
+          'Scrub',
+          'Deep Scrub',
+          'Reweight',
+          'Mark Out',
+          'Mark In',
+          'Mark Down'
+        ],
+        primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' }
       },
       'create,delete': {
         actions: ['Create', 'Mark Lost', 'Purge', 'Destroy'],
@@ -270,6 +286,7 @@ describe('OsdListComponent', () => {
       },
       'update,delete': {
         actions: [
+          'Edit',
           'Scrub',
           'Deep Scrub',
           'Reweight',
@@ -280,11 +297,11 @@ describe('OsdListComponent', () => {
           'Purge',
           'Destroy'
         ],
-        primary: { multiple: 'Scrub', executing: 'Scrub', single: 'Scrub', no: 'Scrub' }
+        primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' }
       },
       update: {
-        actions: ['Scrub', 'Deep Scrub', 'Reweight', 'Mark Out', 'Mark In', 'Mark Down'],
-        primary: { multiple: 'Scrub', executing: 'Scrub', single: 'Scrub', no: 'Scrub' }
+        actions: ['Edit', 'Scrub', 'Deep Scrub', 'Reweight', 'Mark Out', 'Mark In', 'Mark Down'],
+        primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' }
       },
       delete: {
         actions: ['Mark Lost', 'Purge', 'Destroy'],
@@ -350,6 +367,10 @@ describe('OsdListComponent', () => {
       expectOpensModal('Reweight', OsdReweightModalComponent);
     });
 
+    it('opens the form modal', () => {
+      expectOpensModal('Edit', FormModalComponent);
+    });
+
     it('opens all confirmation modals', () => {
       const modalClass = ConfirmationModalComponent;
       expectOpensModal('Mark Out', modalClass);
index 95bf1279c01d5c714361d6bdebeabc2e59756dfd..89842cbf0fe3024e6ac57ad16203b5535f65da31 100644 (file)
@@ -8,16 +8,19 @@ import { forkJoin as observableForkJoin, Observable } from 'rxjs';
 import { OsdService } from '../../../../shared/api/osd.service';
 import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
 import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { FormModalComponent } from '../../../../shared/components/form-modal/form-modal.component';
 import { ActionLabelsI18n } from '../../../../shared/constants/app.constants';
 import { TableComponent } from '../../../../shared/datatable/table/table.component';
 import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
 import { Icons } from '../../../../shared/enum/icons.enum';
+import { NotificationType } from '../../../../shared/enum/notification-type.enum';
 import { CdTableAction } from '../../../../shared/models/cd-table-action';
 import { CdTableColumn } from '../../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
 import { Permissions } from '../../../../shared/models/permissions';
 import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
 import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
+import { NotificationService } from '../../../../shared/services/notification.service';
 import { URLBuilderService } from '../../../../shared/services/url-builder.service';
 import { OsdFlagsModalComponent } from '../osd-flags-modal/osd-flags-modal.component';
 import { OsdPgScrubModalComponent } from '../osd-pg-scrub-modal/osd-pg-scrub-modal.component';
@@ -76,7 +79,8 @@ export class OsdListComponent implements OnInit {
     private modalService: BsModalService,
     private i18n: I18n,
     private urlBuilder: URLBuilderService,
-    public actionLabels: ActionLabelsI18n
+    public actionLabels: ActionLabelsI18n,
+    public notificationService: NotificationService
   ) {
     this.permissions = this.authStorageService.getPermissions();
     this.tableActions = [
@@ -87,6 +91,12 @@ export class OsdListComponent implements OnInit {
         routerLink: () => this.urlBuilder.getCreate(),
         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
       },
+      {
+        name: this.actionLabels.EDIT,
+        permission: 'update',
+        icon: Icons.edit,
+        click: () => this.editAction()
+      },
       {
         name: this.actionLabels.SCRUB,
         permission: 'update',
@@ -324,6 +334,39 @@ export class OsdListComponent implements OnInit {
     });
   }
 
+  editAction() {
+    const selectedOsd = _.filter(this.osds, ['id', this.selection.first().id]).pop();
+
+    this.modalService.show(FormModalComponent, {
+      initialState: {
+        titleText: this.i18n('Edit OSD: {{id}}', {
+          id: selectedOsd.id
+        }),
+        fields: [
+          {
+            type: 'inputText',
+            name: 'deviceClass',
+            value: selectedOsd.tree.device_class,
+            label: this.i18n('Device class'),
+            required: true
+          }
+        ],
+        submitButtonText: this.i18n('Edit OSD'),
+        onSubmit: (values) => {
+          this.osdService.update(selectedOsd.id, values.deviceClass).subscribe(() => {
+            this.notificationService.show(
+              NotificationType.success,
+              this.i18n('Updated OSD "{{id}}"', {
+                id: selectedOsd.id
+              })
+            );
+            this.getOsdList();
+          });
+        }
+      }
+    });
+  }
+
   scrubAction(deep) {
     if (!this.hasOsdSelected) {
       return;
index 957c646dd3dfccbc60b7d9be96cf1f7e1efd78fc..cc92721c09d7f4a3f24fccc0b28d1d82495873df 100644 (file)
@@ -88,6 +88,13 @@ describe('OsdService', () => {
     expect(req.request.body).toEqual({ weight: 0.5 });
   });
 
+  it('should update OSD', () => {
+    service.update(1, 'hdd').subscribe();
+    const req = httpTesting.expectOne('api/osd/1');
+    expect(req.request.method).toBe('PUT');
+    expect(req.request.body).toEqual({ device_class: 'hdd' });
+  });
+
   it('should mark an OSD lost', () => {
     service.markLost(1).subscribe();
     const req = httpTesting.expectOne('api/osd/1/mark_lost');
index e8429342dfd7c039eb1c7b79a7162cdb7a1e3218..d78093e35ae370318fdd53380dee434b0b76f084 100644 (file)
@@ -234,6 +234,10 @@ export class OsdService {
     return this.http.post(`${this.path}/${id}/reweight`, { weight: weight });
   }
 
+  update(id: number, deviceClass: string) {
+    return this.http.put(`${this.path}/${id}`, { device_class: deviceClass });
+  }
+
   markLost(id: number) {
     return this.http.post(`${this.path}/${id}/mark_lost`, null);
   }