]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add OSD recovery speed form
authorTatjana Dehler <tdehler@suse.com>
Thu, 18 Oct 2018 12:57:39 +0000 (14:57 +0200)
committerTatjana Dehler <tdehler@suse.com>
Wed, 5 Dec 2018 13:20:33 +0000 (14:20 +0100)
Add a form to set the OSD recovery speed. There are three predefined
priorities available: 'Low', 'Default' and 'High'.
By setting the priority the config options 'osd_max_backfills',
'osd_recovery_max_active', 'osd_recovery_max_single_start',
'osd_recovery_sleep' will be updated with predefined values.
It's also possible to set custom values.

Fixes: https://tracker.ceph.com/issues/35540
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
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.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.scss [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.spec.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.priorities.ts [new file with mode: 0755]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts
src/pybind/mgr/dashboard/frontend/src/locale/messages.xlf

index bf2177d9878fdc00418f353d318c57df248ad2e5..2adb5a015af93201af0cf4a0fd27fa9ffa30f836 100644 (file)
@@ -24,6 +24,7 @@ import { OsdDetailsComponent } from './osd/osd-details/osd-details.component';
 import { OsdFlagsModalComponent } from './osd/osd-flags-modal/osd-flags-modal.component';
 import { OsdListComponent } from './osd/osd-list/osd-list.component';
 import { OsdPerformanceHistogramComponent } from './osd/osd-performance-histogram/osd-performance-histogram.component';
+import { OsdRecvSpeedModalComponent } from './osd/osd-recv-speed-modal/osd-recv-speed-modal.component';
 import { OsdReweightModalComponent } from './osd/osd-reweight-modal/osd-reweight-modal.component';
 import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.component';
 
@@ -32,6 +33,7 @@ import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.co
     OsdDetailsComponent,
     OsdScrubModalComponent,
     OsdFlagsModalComponent,
+    OsdRecvSpeedModalComponent,
     OsdReweightModalComponent
   ],
   imports: [
@@ -62,7 +64,8 @@ import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.co
     ConfigurationFormComponent,
     OsdReweightModalComponent,
     CrushmapComponent,
-    LogsComponent
+    LogsComponent,
+    OsdRecvSpeedModalComponent
   ]
 })
 export class ClusterModule {}
index 206de6d3ec2b3e18a872cbad8f51f21a78a26bf9..d743bf188798cde88af3f9ac1b6f44bd92ebff94 100644 (file)
           <i class="fa fa-fw fa-cog"
              aria-hidden="true">
           </i>
-          <ng-container i18n>Set Cluster-wide OSD Flags</ng-container>
+          <ng-container i18n>Set Cluster-wide Flags</ng-container>
+        </button>
+
+        <button class="btn btn-sm btn-default btn-label tc_configureCluster"
+                type="button"
+                (click)="configureQosParamsAction()">
+          <i class="fa fa-fw fa-cog"
+             aria-hidden="true">
+          </i>
+          <ng-container i18n>Set Cluster-wide Recovery Priority</ng-container>
         </button>
       </div>
 
index e685e1de85a85f31ac6ff25af33eccc79db34dbb..261d8e5e13ef96c6c5fe6e60c1f8d71d506482bd 100644 (file)
@@ -16,6 +16,7 @@ import { Permissions } from '../../../../shared/models/permissions';
 import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
 import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
 import { OsdFlagsModalComponent } from '../osd-flags-modal/osd-flags-modal.component';
+import { OsdRecvSpeedModalComponent } from '../osd-recv-speed-modal/osd-recv-speed-modal.component';
 import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component';
 import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
 
@@ -301,4 +302,8 @@ export class OsdListComponent implements OnInit {
       });
     });
   }
+
+  configureQosParamsAction() {
+    this.bsModalRef = this.modalService.show(OsdRecvSpeedModalComponent, {});
+  }
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html
new file mode 100755 (executable)
index 0000000..7a7dc9b
--- /dev/null
@@ -0,0 +1,82 @@
+<cd-modal [modalRef]="bsModalRef">
+  <ng-container class="modal-title"
+                i18n>OSD Recovery Priority</ng-container>
+
+  <ng-container class="modal-content">
+    <form class="form-horizontal"
+          #formDir="ngForm"
+          [formGroup]="osdRecvSpeedForm"
+          novalidate>
+      <div class="modal-body">
+        <!-- Priority -->
+        <div class="form-group"
+             [ngClass]="{'has-error': osdRecvSpeedForm.showError('priority', formDir)}">
+          <label class="control-label col-sm-6"
+                 for="priority">
+            <ng-container i18n>Priority</ng-container>
+            <span class="required"></span>
+          </label>
+          <div class="col-sm-6">
+            <select class="form-control"
+                    formControlName="priority"
+                    id="priority"
+                    (change)="onPriorityChange($event.target.value)">
+              <option *ngFor="let priority of priorities"
+                      [value]="priority.name">
+                {{ priority.text }}
+              </option>
+            </select>
+            <span class="help-block"
+                  *ngIf="osdRecvSpeedForm.showError('priority', formDir, 'required')"
+                  i18n>This field is required.</span>
+          </div>
+        </div>
+
+        <!-- Customize priority -->
+        <div class="form-group">
+          <div class="col-sm-offset-6 col-sm-6">
+            <div class="checkbox checkbox-primary">
+              <input formControlName="customizePriority"
+                     id="customizePriority"
+                     type="checkbox"
+                     (change)="onCustomizePriorityChange()">
+              <label i18n
+                     for="customizePriority">Customize priority values</label>
+            </div>
+          </div>
+        </div>
+        <!-- Priority values -->
+        <div class="form-group" *ngFor="let attr of priorityAttrs"
+             [ngClass]="{'has-error': osdRecvSpeedForm.getValue('customizePriority') &&
+             osdRecvSpeedForm.showError(attr.name, formDir)}">
+          <label class="control-label col-sm-6"
+                 [for]="attr.name">{{ attr.text }}
+            <span class="required" *ngIf="osdRecvSpeedForm.getValue('customizePriority')"></span>
+          </label>
+          <div class="col-sm-6">
+            <input class="form-control"
+                   type="number"
+                   [id]="attr.name"
+                   [formControlName]="attr.name"
+                   [readonly]="!osdRecvSpeedForm.getValue('customizePriority')">
+            <span class="help-block"
+                  *ngIf="osdRecvSpeedForm.getValue('customizePriority') &&
+                  osdRecvSpeedForm.showError(attr.name, formDir, 'required')"
+                  i18n>This field is required!</span>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <div class="button-group text-right">
+          <cd-submit-button (submitAction)="submitAction()"
+                            [form]="osdRecvSpeedForm"
+                            i18n>Submit</cd-submit-button>
+
+          <button class="btn btn-link btn-sm"
+                  (click)="bsModalRef.hide()"
+                  i18n>Cancel</button>
+        </div>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.scss
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.spec.ts
new file mode 100755 (executable)
index 0000000..3336ed8
--- /dev/null
@@ -0,0 +1,364 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+
+import * as _ from 'lodash';
+import { ToastModule } from 'ng2-toastr';
+import { BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
+import { of } from 'rxjs';
+
+import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import { ConfigurationService } from '../../../../shared/api/configuration.service';
+import { SharedModule } from '../../../../shared/shared.module';
+import { OsdRecvSpeedModalComponent } from './osd-recv-speed-modal.component';
+
+describe('OsdRecvSpeedModalComponent', () => {
+  let component: OsdRecvSpeedModalComponent;
+  let fixture: ComponentFixture<OsdRecvSpeedModalComponent>;
+  let configService: ConfigurationService;
+
+  configureTestBed({
+    imports: [
+      HttpClientTestingModule,
+      ModalModule.forRoot(),
+      ReactiveFormsModule,
+      SharedModule,
+      ToastModule.forRoot()
+    ],
+    declarations: [OsdRecvSpeedModalComponent],
+    providers: [BsModalRef, i18nProviders]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(OsdRecvSpeedModalComponent);
+    component = fixture.componentInstance;
+    configService = TestBed.get(ConfigurationService);
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('setPriority', () => {
+    it('should prepare the form for a custom priority', () => {
+      const customPriority = {
+        name: 'custom',
+        text: 'Custom',
+        values: {
+          osd_max_backfills: 1,
+          osd_recovery_max_active: 4,
+          osd_recovery_max_single_start: 1,
+          osd_recovery_sleep: 1
+        }
+      };
+
+      component.setPriority(customPriority);
+
+      const customInPriorities = _.find(component.priorities, (p) => {
+        return p.name === 'custom';
+      });
+
+      expect(customInPriorities).not.toBeNull();
+      expect(component.osdRecvSpeedForm.getValue('priority')).toBe('custom');
+      expect(component.osdRecvSpeedForm.getValue('osd_max_backfills')).toBe(1);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_max_active')).toBe(4);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_max_single_start')).toBe(1);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_sleep')).toBe(1);
+    });
+
+    it('should prepare the form for a none custom priority', () => {
+      const lowPriority = {
+        name: 'low',
+        text: 'Low',
+        values: {
+          osd_max_backfills: 1,
+          osd_recovery_max_active: 1,
+          osd_recovery_max_single_start: 1,
+          osd_recovery_sleep: 0.5
+        }
+      };
+
+      component.setPriority(lowPriority);
+
+      const customInPriorities = _.find(component.priorities, (p) => {
+        return p.name === 'custom';
+      });
+
+      expect(customInPriorities).toBeUndefined();
+      expect(component.osdRecvSpeedForm.getValue('priority')).toBe('low');
+      expect(component.osdRecvSpeedForm.getValue('osd_max_backfills')).toBe(1);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_max_active')).toBe(1);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_max_single_start')).toBe(1);
+      expect(component.osdRecvSpeedForm.getValue('osd_recovery_sleep')).toBe(0.5);
+    });
+  });
+
+  describe('getStoredPriority', () => {
+    const configOptionsLow = [
+      {
+        name: 'osd_max_backfills',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_active',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_sleep',
+        value: [
+          {
+            section: 'osd',
+            value: '0.5'
+          }
+        ]
+      }
+    ];
+
+    const configOptionsDefault = [
+      {
+        name: 'osd_max_backfills',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_active',
+        value: [
+          {
+            section: 'osd',
+            value: '3'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_sleep',
+        value: [
+          {
+            section: 'osd',
+            value: '0'
+          }
+        ]
+      }
+    ];
+
+    const configOptionsHigh = [
+      {
+        name: 'osd_max_backfills',
+        value: [
+          {
+            section: 'osd',
+            value: '4'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_active',
+        value: [
+          {
+            section: 'osd',
+            value: '4'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        value: [
+          {
+            section: 'osd',
+            value: '4'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_sleep',
+        value: [
+          {
+            section: 'osd',
+            value: '0'
+          }
+        ]
+      }
+    ];
+
+    const configOptionsCustom = [
+      {
+        name: 'osd_max_backfills',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_active',
+        value: [
+          {
+            section: 'osd',
+            value: '2'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_sleep',
+        value: [
+          {
+            section: 'osd',
+            value: '0'
+          }
+        ]
+      }
+    ];
+
+    const configOptionsIncomplete = [
+      {
+        name: 'osd_max_backfills',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        value: [
+          {
+            section: 'osd',
+            value: '1'
+          }
+        ]
+      },
+      {
+        name: 'osd_recovery_sleep',
+        value: [
+          {
+            section: 'osd',
+            value: '0'
+          }
+        ]
+      }
+    ];
+
+    it('should return priority "low" if the config option values have been set accordingly', fakeAsync(() => {
+      spyOn(configService, 'get').and.callFake((configOptionName: string) => {
+        const result = _.find(configOptionsLow, (configOption) => {
+          return configOption.name === configOptionName;
+        });
+        return of(result);
+      });
+
+      component.getStoredPriority((priority) => {
+        expect(priority.name).toBe('low');
+      });
+      tick();
+
+      expect(component.osdRecvSpeedForm.getValue('customizePriority')).toBeFalsy();
+    }));
+
+    it('should return priority "default" if the config option values have been set accordingly', fakeAsync(() => {
+      spyOn(configService, 'get').and.callFake((configOptionName: string) => {
+        const result = _.find(configOptionsDefault, (configOption) => {
+          return configOption.name === configOptionName;
+        });
+        return of(result);
+      });
+
+      component.getStoredPriority((priority) => {
+        expect(priority.name).toBe('default');
+      });
+      tick();
+
+      expect(component.osdRecvSpeedForm.getValue('customizePriority')).toBeFalsy();
+    }));
+
+    it('should return priority "high" if the config option values have been set accordingly', fakeAsync(() => {
+      spyOn(configService, 'get').and.callFake((configOptionName: string) => {
+        const result = _.find(configOptionsHigh, (configOption) => {
+          return configOption.name === configOptionName;
+        });
+        return of(result);
+      });
+
+      component.getStoredPriority((priority) => {
+        expect(priority.name).toBe('high');
+      });
+      tick();
+
+      expect(component.osdRecvSpeedForm.getValue('customizePriority')).toBeFalsy();
+    }));
+
+    it('should return priority "custom" if the config option values do not match any priority', fakeAsync(() => {
+      spyOn(configService, 'get').and.callFake((configOptionName: string) => {
+        const result = _.find(configOptionsCustom, (configOption) => {
+          return configOption.name === configOptionName;
+        });
+        return of(result);
+      });
+
+      component.getStoredPriority((priority) => {
+        expect(priority.name).toBe('custom');
+      });
+      tick();
+
+      expect(component.osdRecvSpeedForm.getValue('customizePriority')).toBeTruthy();
+    }));
+
+    it('should return no priority if the config option values are incomplete', fakeAsync(() => {
+      spyOn(configService, 'get').and.callFake((configOptionName: string) => {
+        const result = _.find(configOptionsIncomplete, (configOption) => {
+          return configOption.name === configOptionName;
+        });
+        return of(result);
+      });
+
+      component.getStoredPriority((priority) => {
+        expect(priority.name).toBeNull();
+      });
+      tick();
+
+      expect(component.osdRecvSpeedForm.getValue('customizePriority')).toBeFalsy();
+    }));
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts
new file mode 100755 (executable)
index 0000000..948bea2
--- /dev/null
@@ -0,0 +1,180 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { forkJoin as observableForkJoin, of } from 'rxjs';
+import { mergeMap } from 'rxjs/operators';
+
+import { ConfigurationService } from '../../../../shared/api/configuration.service';
+import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
+import { NotificationService } from '../../../../shared/services/notification.service';
+import { OsdRecvSpeedModalPriorities } from './osd-recv-speed-modal.priorities';
+
+@Component({
+  selector: 'cd-osd-recv-speed-modal',
+  templateUrl: './osd-recv-speed-modal.component.html',
+  styleUrls: ['./osd-recv-speed-modal.component.scss']
+})
+export class OsdRecvSpeedModalComponent implements OnInit {
+  osdRecvSpeedForm: CdFormGroup;
+  priorities = OsdRecvSpeedModalPriorities.KNOWN_PRIORITIES;
+  priorityAttrs = [];
+
+  constructor(
+    public bsModalRef: BsModalRef,
+    private configService: ConfigurationService,
+    private notificationService: NotificationService,
+    private i18n: I18n
+  ) {
+    this.osdRecvSpeedForm = new CdFormGroup({
+      priority: new FormControl(null, { validators: [Validators.required] }),
+      customizePriority: new FormControl(false)
+    });
+    this.priorityAttrs = [
+      {
+        name: 'osd_max_backfills',
+        text: this.i18n('Max Backfills')
+      },
+      {
+        name: 'osd_recovery_max_active',
+        text: this.i18n('Recovery Max Active')
+      },
+      {
+        name: 'osd_recovery_max_single_start',
+        text: this.i18n('Recovery Max Single Start')
+      },
+      {
+        name: 'osd_recovery_sleep',
+        text: this.i18n('Recovery Sleep')
+      }
+    ];
+
+    this.priorityAttrs.forEach((attr) => {
+      this.osdRecvSpeedForm.addControl(
+        attr.name,
+        new FormControl(null, { validators: [Validators.required] })
+      );
+    });
+  }
+
+  ngOnInit() {
+    this.getStoredPriority((priority) => {
+      this.setPriority(priority);
+    });
+  }
+
+  setPriority(priority: any) {
+    const customPriority = _.find(this.priorities, (p) => {
+      return p.name === 'custom';
+    });
+
+    if (priority.name === 'custom') {
+      if (!customPriority) {
+        this.priorities.push(priority);
+      }
+    } else {
+      if (customPriority) {
+        this.priorities.splice(this.priorities.indexOf(customPriority), 1);
+      }
+    }
+
+    this.osdRecvSpeedForm.controls.priority.setValue(priority.name);
+    Object.entries(priority.values).forEach(([name, value]) => {
+      this.osdRecvSpeedForm.controls[name].setValue(value);
+    });
+  }
+
+  onCustomizePriorityChange() {
+    if (this.osdRecvSpeedForm.getValue('customizePriority')) {
+      const values = {};
+      this.priorityAttrs.forEach((attr) => {
+        values[attr.name] = this.osdRecvSpeedForm.getValue(attr.name);
+      });
+      const customPriority = {
+        name: 'custom',
+        text: this.i18n('Custom'),
+        values: values
+      };
+      this.setPriority(customPriority);
+    } else {
+      this.setPriority(this.priorities[0]);
+    }
+  }
+
+  getStoredPriority(callbackFn: Function) {
+    const observables = [];
+    this.priorityAttrs.forEach((configName) => {
+      observables.push(this.configService.get(configName.name));
+    });
+
+    observableForkJoin(observables)
+      .pipe(
+        mergeMap((configOptions) => {
+          const result = {};
+          configOptions.forEach((configOption) => {
+            if (configOption && 'value' in configOption) {
+              configOption.value.forEach((value) => {
+                if (value['section'] === 'osd') {
+                  result[configOption.name] = Number(value.value);
+                }
+              });
+            }
+          });
+          return of(result);
+        })
+      )
+      .subscribe((resp) => {
+        const priority = _.find(this.priorities, (p) => {
+          return _.isEqual(p.values, resp);
+        });
+
+        this.osdRecvSpeedForm.controls.customizePriority.setValue(false);
+
+        if (priority) {
+          return callbackFn(priority);
+        }
+
+        if (Object.entries(resp).length === 4) {
+          this.osdRecvSpeedForm.controls.customizePriority.setValue(true);
+          return callbackFn(Object({ name: 'custom', text: this.i18n('Custom'), values: resp }));
+        }
+
+        return callbackFn(this.priorities[0]);
+      });
+  }
+
+  onPriorityChange(selectedPriorityName) {
+    const selectedPriority =
+      _.find(this.priorities, (p) => {
+        return p.name === selectedPriorityName;
+      }) || this.priorities[0];
+
+    this.setPriority(selectedPriority);
+  }
+
+  submitAction() {
+    const options = {};
+    this.priorityAttrs.forEach((attr) => {
+      options[attr.name] = { section: 'osd', value: this.osdRecvSpeedForm.getValue(attr.name) };
+    });
+
+    this.configService.bulkCreate({ options: options }).subscribe(
+      () => {
+        this.notificationService.show(
+          NotificationType.success,
+          this.i18n('OSD recovery speed priority "{{value}}" was set successfully.', {
+            value: this.osdRecvSpeedForm.getValue('priority')
+          }),
+          this.i18n('OSD recovery speed priority')
+        );
+        this.bsModalRef.hide();
+      },
+      () => {
+        this.bsModalRef.hide();
+      }
+    );
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.priorities.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.priorities.ts
new file mode 100755 (executable)
index 0000000..7b1bacd
--- /dev/null
@@ -0,0 +1,45 @@
+export class OsdRecvSpeedModalPriorities {
+  public static KNOWN_PRIORITIES = [
+    // TODO: I18n
+    {
+      name: null,
+      text: '-- Select the priority --',
+      values: {
+        osd_max_backfills: null,
+        osd_recovery_max_active: null,
+        osd_recovery_max_single_start: null,
+        osd_recovery_sleep: null
+      }
+    },
+    {
+      name: 'low',
+      text: 'Low',
+      values: {
+        osd_max_backfills: 1,
+        osd_recovery_max_active: 1,
+        osd_recovery_max_single_start: 1,
+        osd_recovery_sleep: 0.5
+      }
+    },
+    {
+      name: 'default',
+      text: 'Default',
+      values: {
+        osd_max_backfills: 1,
+        osd_recovery_max_active: 3,
+        osd_recovery_max_single_start: 1,
+        osd_recovery_sleep: 0
+      }
+    },
+    {
+      name: 'high',
+      text: 'High',
+      values: {
+        osd_max_backfills: 4,
+        osd_recovery_max_active: 4,
+        osd_recovery_max_single_start: 4,
+        osd_recovery_sleep: 0
+      }
+    }
+  ];
+}
index 6e7a98d8f31ab803ab053d2d80d7fcd30c25f6f2..a977a17d180e7e6c24c45bbe97197f74450c3664 100644 (file)
@@ -21,4 +21,8 @@ export class ConfigurationService {
   create(configOption: ConfigFormCreateRequestModel) {
     return this.http.post('api/cluster_conf/', configOption);
   }
+
+  bulkCreate(configOptions: Object) {
+    return this.http.put('api/cluster_conf', configOptions);
+  }
 }
index 11343488773b1ff93a490df1ffb4bf91372c5342..7ffbb43fdae9ecab64c5dbdc8f747a538ffbe1a4 100644 (file)
           <context context-type="sourcefile">app/ceph/block/rbd-form/rbd-form.component.html</context>
           <context context-type="linenumber">175</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">31</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.html</context>
           <context context-type="linenumber">18</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
-          <context context-type="linenumber">50</context>
+          <context context-type="linenumber">59</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/pool/pool-list/pool-list.component.html</context>
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.html</context>
           <context context-type="linenumber">38</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">77</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.html</context>
           <context context-type="linenumber">34</context>
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-flags-modal/osd-flags-modal.component.html</context>
           <context context-type="linenumber">34</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">73</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.html</context>
           <context context-type="linenumber">21</context>
         </context-group>
+      </trans-unit><trans-unit id="c35f9c5f268a514b970cc55e9a5dc4bed0988e7f" datatype="html">
+        <source>OSD Recovery Priority</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit><trans-unit id="b74af38005e8a8914e45af2ec412e11ceafef8b6" datatype="html">
+        <source>Priority</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit><trans-unit id="c2f48f04b379bfba133825747adfd238d511412e" datatype="html">
+        <source>Customize priority values</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">44</context>
+        </context-group>
+      </trans-unit><trans-unit id="4aa19de2a2b54cbda39e9c62917b23044c087776" datatype="html">
+        <source>This field is required!</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.html</context>
+          <context context-type="linenumber">65</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
+          <context context-type="linenumber">34</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
+          <context context-type="linenumber">68</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
+          <context context-type="linenumber">118</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
+          <context context-type="linenumber">166</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
+          <context context-type="linenumber">40</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
+          <context context-type="linenumber">69</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
+          <context context-type="linenumber">95</context>
+        </context-group>
       </trans-unit><trans-unit id="272696ff8acdbed0af6dc13f09729e70be435b82" datatype="html">
         <source>Reweight OSD</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.html</context>
           <context context-type="linenumber">13</context>
         </context-group>
-      </trans-unit><trans-unit id="5e5d841e1b1db3f5d3cf9a7549dea652e08856ed" datatype="html">
-        <source>Set Cluster-wide OSD Flags</source>
+      </trans-unit><trans-unit id="4beacb61e2236200b5627677b5c0054d2b482ed4" datatype="html">
+        <source>Set Cluster-wide Flags</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
           <context context-type="linenumber">23</context>
         </context-group>
+      </trans-unit><trans-unit id="9617df8e0504d997d0ff45b6c206a12becd13c37" datatype="html">
+        <source>Set Cluster-wide Recovery Priority</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
+          <context context-type="linenumber">32</context>
+        </context-group>
       </trans-unit><trans-unit id="b49d7877d24112d4bdfce9256edf61a007fae888" datatype="html">
         <source>OSDs List</source>
         <context-group purpose="location">
   <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ markActionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> if you proceed.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
-          <context context-type="linenumber">65</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit><trans-unit id="2d3a73f6440a7d896d74356fe0a725d731e71cbb" datatype="html">
         <source>The OSD is not safe to destroy!</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
-          <context context-type="linenumber">74</context>
+          <context context-type="linenumber">83</context>
         </context-group>
       </trans-unit><trans-unit id="9d08116242443953ebbfe10bc2092e0a694b4adf" datatype="html">
         <source><x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>OSD <x id="INTERPOLATION" equiv-text="{{ selection.first().id }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> will be
   <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ actionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> if you proceed.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">85</context>
         </context-group>
       </trans-unit><trans-unit id="d2bcd3296d2850de762fb943060b7e086a893181" datatype="html">
         <source>Health</source>
           <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
           <context context-type="linenumber">3</context>
         </context-group>
-      </trans-unit><trans-unit id="4aa19de2a2b54cbda39e9c62917b23044c087776" datatype="html">
-        <source>This field is required!</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
-          <context context-type="linenumber">34</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
-          <context context-type="linenumber">68</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
-          <context context-type="linenumber">92</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
-          <context context-type="linenumber">118</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
-          <context context-type="linenumber">166</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
-          <context context-type="linenumber">40</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
-          <context context-type="linenumber">69</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
-          <context context-type="linenumber">95</context>
-        </context-group>
       </trans-unit><trans-unit id="9edc2b494e660618af3e5225f68d40b7ca67629c" datatype="html">
         <source>The chosen erasure code profile name is already in use.</source>
         <context-group purpose="location">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4edc2162af0bd6cd941eaf730d20a7e3b5a83ba7" datatype="html">
+        <source>Max Backfills</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="eb5e6c24a48a1dfe23e0a772b762be5e949c0c8a" datatype="html">
+        <source>Recovery Max Active</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a23c6d3859e03184118620d2baeb736ac765fbf" datatype="html">
+        <source>Recovery Max Single Start</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="97049169ef5228fd1724de2fcdad1fec2858eaf8" datatype="html">
+        <source>Recovery Sleep</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a5c05002b0ac2040f1aede5e727e0ffd06eda819" datatype="html">
+        <source>Custom</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="92a8cbd0b31b1a5a64fe3a3245437ecf14b6d74d" datatype="html">
+        <source>OSD recovery speed priority &quot;<x id="INTERPOLATION" equiv-text="{{value}}"/>&quot; was set successfully.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="87241d8eb7bf0d14756f27febe8d0a84015627db" datatype="html">
+        <source>OSD recovery speed priority</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/ceph/cluster/osd/osd-recv-speed-modal/osd-recv-speed-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="442db228d78b94d704b22bb0ffeb712af183f19a" datatype="html">
         <source><x id="INTERPOLATION" equiv-text="{{operation}}"/> was initialized in the following OSD: <x id="INTERPOLATION_1" equiv-text="{{id}}"/></source>
         <context-group purpose="location">