]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add PG scrub configuration form
authorTatjana Dehler <tdehler@suse.com>
Fri, 1 Mar 2019 15:11:53 +0000 (16:11 +0100)
committerTatjana Dehler <tdehler@suse.com>
Fri, 14 Jun 2019 13:52:49 +0000 (15:52 +0200)
The commit adapts two different parts:

1. It adds all frontend related changes around the PG scrub
   configuration form
2. It also adds an API test case to check the existence of
   all hard-coded config options in the frontend

Fixes: https://tracker.ceph.com/issues/38211
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
(cherry picked from commit 808d7cb39ea9c1093c2d6cdd11f22920a05381a8)

qa/tasks/mgr/dashboard/test_cluster_configuration.py
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-pg-scrub-modal/osd-pg-scrub-modal.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts [new file with mode: 0644]

index 2a718447c1c6160da0d348480d5bee7125daa44a..b3abc146c0f427fff536167413bc4783fe20385e 100644 (file)
@@ -229,6 +229,57 @@ class ClusterConfigurationTest(DashboardTestCase):
                                                         value)
             self.assertEqual(result, value)
 
+    def test_check_existence(self):
+        """
+        This test case is intended to check the existence of all hard coded config options used by
+        the dashboard.
+
+        If you include further hard coded options in the dashboard, feel free to add them to the
+        list.
+        """
+        hard_coded_options = [
+            'osd_max_backfills',  # osd-recv-speed
+            'osd_recovery_max_active',  # osd-recv-speed
+            'osd_recovery_max_single_start',  # osd-recv-speed
+            'osd_recovery_sleep',  # osd-recv-speed
+            'osd_scrub_during_recovery',  # osd-pg-scrub
+            'osd_scrub_begin_hour',  # osd-pg-scrub
+            'osd_scrub_end_hour',  # osd-pg-scrub
+            'osd_scrub_begin_week_day',  # osd-pg-scrub
+            'osd_scrub_end_week_day',  # osd-pg-scrub
+            'osd_scrub_min_interval',  # osd-pg-scrub
+            'osd_scrub_max_interval',  # osd-pg-scrub
+            'osd_deep_scrub_interval',  # osd-pg-scrub
+            'osd_scrub_auto_repair',  # osd-pg-scrub
+            'osd_max_scrubs',  # osd-pg-scrub
+            'osd_scrub_priority',  # osd-pg-scrub
+            'osd_scrub_sleep',  # osd-pg-scrub
+            'osd_scrub_auto_repair_num_errors',  # osd-pg-scrub
+            'osd_debug_deep_scrub_sleep',  # osd-pg-scrub
+            'osd_deep_scrub_keys',  # osd-pg-scrub
+            'osd_deep_scrub_large_omap_object_key_threshold',  # osd-pg-scrub
+            'osd_deep_scrub_large_omap_object_value_sum_threshold',  # osd-pg-scrub
+            'osd_deep_scrub_randomize_ratio',  # osd-pg-scrub
+            'osd_deep_scrub_stride',  # osd-pg-scrub
+            'osd_deep_scrub_update_digest_min_age',  # osd-pg-scrub
+            'osd_op_queue_mclock_scrub_lim',  # osd-pg-scrub
+            'osd_op_queue_mclock_scrub_res',  # osd-pg-scrub
+            'osd_op_queue_mclock_scrub_wgt',  # osd-pg-scrub
+            'osd_requested_scrub_priority',  # osd-pg-scrub
+            'osd_scrub_backoff_ratio',  # osd-pg-scrub
+            'osd_scrub_chunk_max',  # osd-pg-scrub
+            'osd_scrub_chunk_min',  # osd-pg-scrub
+            'osd_scrub_cost',  # osd-pg-scrub
+            'osd_scrub_interval_randomize_ratio',  # osd-pg-scrub
+            'osd_scrub_invalid_stats',  # osd-pg-scrub
+            'osd_scrub_load_threshold',  # osd-pg-scrub
+            'osd_scrub_max_preemptions'  # osd-pg-scrub
+        ]
+
+        for config_option in hard_coded_options:
+            self._get('/api/cluster_conf/{}'.format(config_option))
+            self.assertStatus(200)
+
     def _validate_single(self, data):
         self.assertIn('name', data)
         self.assertIn('daemon_default', data)
index fce9b6ab98826bf22251ce233d444adea4c04785..4f19f14e8db9d56ef39703318188f15f0ff5d16a 100644 (file)
@@ -27,6 +27,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 { OsdPgScrubModalComponent } from './osd/osd-pg-scrub-modal/osd-pg-scrub-modal.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';
@@ -38,7 +39,8 @@ import { PrometheusListComponent } from './prometheus/prometheus-list/prometheus
     OsdScrubModalComponent,
     OsdFlagsModalComponent,
     OsdRecvSpeedModalComponent,
-    OsdReweightModalComponent
+    OsdReweightModalComponent,
+    OsdPgScrubModalComponent
   ],
   imports: [
     CommonModule,
@@ -73,7 +75,8 @@ import { PrometheusListComponent } from './prometheus/prometheus-list/prometheus
     CrushmapComponent,
     LogsComponent,
     PrometheusListComponent,
-    OsdRecvSpeedModalComponent
+    OsdRecvSpeedModalComponent,
+    OsdPgScrubModalComponent
   ]
 })
 export class ClusterModule {}
index 86f9afacba79216e2644e68235e2a6c94d8045ea..f7646bc024e00ae62af0622172ca2c2c59fa3fdd 100644 (file)
           <ul *dropdownMenu
               class="dropdown-menu"
               role="menu">
-            <li role="menuitem">
-              <a class="dropdown-item"
-                 (click)="configureQosParamsAction()">
-                <i class="fa fa-fw fa-cog"
-                   aria-hidden="true">
-                </i>
-                <ng-container i18n>Set Cluster-wide Recovery Priority</ng-container>
-              </a>
-            </li>
+            <ng-container *ngFor="let action of generalTableActions">
+              <li role="menuitem">
+                <a class="dropdown-item"
+                   (click)="action.click()">
+                  <i class="fa fa-fw {{ action.icon }}"
+                     aria-hidden="true">
+                  </i>
+                  <ng-container>{{ action.name }}</ng-container>
+                </a>
+              </li>
+            </ng-container>
           </ul>
         </div>
       </div>
index 7fe5abd41b0cee2058b16a61819d585a44170b47..66059f117145f0ecc3ca8633a5c416ab34f2b020 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 { OsdPgScrubModalComponent } from '../osd-pg-scrub-modal/osd-pg-scrub-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';
@@ -45,6 +46,7 @@ export class OsdListComponent implements OnInit {
   tableActions: CdTableAction[];
   bsModalRef: BsModalRef;
   columns: CdTableColumn[];
+  generalTableActions: any[];
 
   osds = [];
   selection = new CdTableSelection();
@@ -144,6 +146,18 @@ export class OsdListComponent implements OnInit {
         icon: 'fa-remove'
       }
     ];
+    this.generalTableActions = [
+      {
+        name: this.i18n('Set Cluster-wide Recovery Priority'),
+        icon: 'fa-cog',
+        click: () => this.configureQosParamsAction()
+      },
+      {
+        name: this.i18n('PG scrub configuration'),
+        icon: 'fa-stethoscope',
+        click: () => this.configurePgScrubAction()
+      }
+    ];
   }
 
   ngOnInit() {
@@ -306,4 +320,8 @@ export class OsdListComponent implements OnInit {
   configureQosParamsAction() {
     this.bsModalRef = this.modalService.show(OsdRecvSpeedModalComponent, {});
   }
+
+  configurePgScrubAction() {
+    this.bsModalRef = this.modalService.show(OsdPgScrubModalComponent, { class: 'modal-lg' });
+  }
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.html
new file mode 100644 (file)
index 0000000..e3ee721
--- /dev/null
@@ -0,0 +1,48 @@
+<cd-modal [modalRef]="bsModalRef">
+  <ng-container i18n="form title|Example: Create Pool@@formTitle"
+                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
+
+  <ng-container class="modal-content">
+    <form class="form-horizontal"
+          #formDir="ngForm"
+          [formGroup]="osdPgScrubForm"
+          novalidate>
+      <div class="modal-body osd-modal">
+        <!-- Basic -->
+        <cd-config-option [optionNames]="basicOptions"
+                          [optionsForm]="osdPgScrubForm"
+                          [optionsFormDir]="formDir"
+                          [optionsFormGroupName]="'basicFormGroup'"
+                          #basicOptionsValues></cd-config-option>
+        <!-- Advanced -->
+        <div class="row">
+          <div class="col-sm-12">
+            <a class="pull-right margin-right-md"
+               (click)="advancedEnabled = true"
+               *ngIf="!advancedEnabled"
+               i18n>Advanced...</a>
+          </div>
+        </div>
+        <div *ngIf="advancedEnabled">
+          <h2 class="page-header"
+              i18n>Advanced configuration options</h2>
+          <cd-config-option [optionNames]="advancedOptions"
+                            [optionsForm]="osdPgScrubForm"
+                            [optionsFormDir]="formDir"
+                            [optionsFormGroupName]="'advancedFormGroup'"
+                            #advancedOptionsValues></cd-config-option>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <div class="button-group text-right">
+          <cd-submit-button (submitAction)="submitAction()"
+                            [form]="osdPgScrubForm"
+                            i18n="form action button|Example: Create Pool@@formActionButton"
+                            type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
+          <cd-back-button [back]="bsModalRef.hide">
+          </cd-back-button>
+        </div>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..beda0bd
--- /dev/null
@@ -0,0 +1,65 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { ToastModule } from 'ng2-toastr';
+import { BsModalRef, ModalModule } from 'ngx-bootstrap/modal';
+import { of as observableOf } from 'rxjs';
+
+import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import { ConfigurationService } from '../../../../shared/api/configuration.service';
+import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { NotificationService } from '../../../../shared/services/notification.service';
+import { SharedModule } from '../../../../shared/shared.module';
+import { OsdPgScrubModalComponent } from './osd-pg-scrub-modal.component';
+
+describe('OsdPgScrubModalComponent', () => {
+  let component: OsdPgScrubModalComponent;
+  let fixture: ComponentFixture<OsdPgScrubModalComponent>;
+  let configurationService: ConfigurationService;
+
+  configureTestBed({
+    imports: [
+      HttpClientTestingModule,
+      ModalModule.forRoot(),
+      ReactiveFormsModule,
+      RouterTestingModule,
+      SharedModule,
+      ToastModule.forRoot()
+    ],
+    declarations: [OsdPgScrubModalComponent],
+    providers: [BsModalRef, i18nProviders]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(OsdPgScrubModalComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+    configurationService = TestBed.get(ConfigurationService);
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('submitAction', () => {
+    let notificationService: NotificationService;
+
+    beforeEach(() => {
+      spyOn(TestBed.get(Router), 'navigate').and.stub();
+      notificationService = TestBed.get(NotificationService);
+      spyOn(notificationService, 'show');
+    });
+
+    it('test create success notification', () => {
+      spyOn(configurationService, 'bulkCreate').and.returnValue(observableOf([]));
+      component.submitAction();
+      expect(notificationService.show).toHaveBeenCalledWith(
+        NotificationType.success,
+        'Updated PG scrub options'
+      );
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts
new file mode 100644 (file)
index 0000000..2b17dba
--- /dev/null
@@ -0,0 +1,65 @@
+import { Component, ViewChild } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { forkJoin as observableForkJoin } from 'rxjs';
+
+import { ConfigOptionComponent } from '../../../../shared/components/config-option/config-option.component';
+import { ActionLabelsI18n } from '../../../../shared/constants/app.constants';
+import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
+import { NotificationService } from '../../../../shared/services/notification.service';
+import { OsdPgScrubModalOptions } from './osd-pg-scrub-modal.options';
+
+@Component({
+  selector: 'cd-osd-pg-scrub-modal',
+  templateUrl: './osd-pg-scrub-modal.component.html',
+  styleUrls: ['./osd-pg-scrub-modal.component.scss']
+})
+export class OsdPgScrubModalComponent {
+  osdPgScrubForm: CdFormGroup;
+  action: string;
+  resource: string;
+
+  @ViewChild('basicOptionsValues')
+  basicOptionsValues: ConfigOptionComponent;
+  basicOptions: Array<string> = OsdPgScrubModalOptions.basicOptions;
+
+  @ViewChild('advancedOptionsValues')
+  advancedOptionsValues: ConfigOptionComponent;
+  advancedOptions: Array<string> = OsdPgScrubModalOptions.advancedOptions;
+
+  advancedEnabled = false;
+
+  constructor(
+    public bsModalRef: BsModalRef,
+    private notificationService: NotificationService,
+    private i18n: I18n,
+    public actionLabels: ActionLabelsI18n
+  ) {
+    this.osdPgScrubForm = new CdFormGroup({});
+    this.resource = this.i18n('PG scrub options');
+    this.action = this.actionLabels.EDIT;
+  }
+
+  submitAction() {
+    const observables = [this.basicOptionsValues.saveValues()];
+
+    if (this.advancedOptionsValues) {
+      observables.push(this.advancedOptionsValues.saveValues());
+    }
+
+    observableForkJoin(observables).subscribe(
+      () => {
+        this.notificationService.show(
+          NotificationType.success,
+          this.i18n('Updated PG scrub options')
+        );
+        this.bsModalRef.hide();
+      },
+      () => {
+        this.bsModalRef.hide();
+      }
+    );
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts
new file mode 100644 (file)
index 0000000..b7c07d1
--- /dev/null
@@ -0,0 +1,39 @@
+export class OsdPgScrubModalOptions {
+  public static basicOptions: Array<string> = [
+    'osd_scrub_during_recovery',
+    'osd_scrub_begin_hour',
+    'osd_scrub_end_hour',
+    'osd_scrub_begin_week_day',
+    'osd_scrub_end_week_day',
+    'osd_scrub_min_interval',
+    'osd_scrub_max_interval',
+    'osd_deep_scrub_interval',
+    'osd_scrub_auto_repair',
+    'osd_max_scrubs',
+    'osd_scrub_priority',
+    'osd_scrub_sleep'
+  ];
+
+  public static advancedOptions: Array<string> = [
+    'osd_scrub_auto_repair_num_errors',
+    'osd_debug_deep_scrub_sleep',
+    'osd_deep_scrub_keys',
+    'osd_deep_scrub_large_omap_object_key_threshold',
+    'osd_deep_scrub_large_omap_object_value_sum_threshold',
+    'osd_deep_scrub_randomize_ratio',
+    'osd_deep_scrub_stride',
+    'osd_deep_scrub_update_digest_min_age',
+    'osd_op_queue_mclock_scrub_lim',
+    'osd_op_queue_mclock_scrub_res',
+    'osd_op_queue_mclock_scrub_wgt',
+    'osd_requested_scrub_priority',
+    'osd_scrub_backoff_ratio',
+    'osd_scrub_chunk_max',
+    'osd_scrub_chunk_min',
+    'osd_scrub_cost',
+    'osd_scrub_interval_randomize_ratio',
+    'osd_scrub_invalid_stats',
+    'osd_scrub_load_threshold',
+    'osd_scrub_max_preemptions'
+  ];
+}