From 7dde7c2ebe220bb9f5a64166305414ea1bc168a9 Mon Sep 17 00:00:00 2001 From: Tatjana Dehler Date: Fri, 1 Mar 2019 16:11:53 +0100 Subject: [PATCH] mgr/dashboard: Add PG scrub configuration form 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 (cherry picked from commit 808d7cb39ea9c1093c2d6cdd11f22920a05381a8) --- .../dashboard/test_cluster_configuration.py | 51 +++++++++++++++ .../src/app/ceph/cluster/cluster.module.ts | 7 +- .../osd/osd-list/osd-list.component.html | 20 +++--- .../osd/osd-list/osd-list.component.ts | 18 +++++ .../osd-pg-scrub-modal.component.html | 48 ++++++++++++++ .../osd-pg-scrub-modal.component.scss | 0 .../osd-pg-scrub-modal.component.spec.ts | 65 +++++++++++++++++++ .../osd-pg-scrub-modal.component.ts | 65 +++++++++++++++++++ .../osd-pg-scrub-modal.options.ts | 39 +++++++++++ 9 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts diff --git a/qa/tasks/mgr/dashboard/test_cluster_configuration.py b/qa/tasks/mgr/dashboard/test_cluster_configuration.py index 2a718447c1c61..b3abc146c0f42 100644 --- a/qa/tasks/mgr/dashboard/test_cluster_configuration.py +++ b/qa/tasks/mgr/dashboard/test_cluster_configuration.py @@ -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) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index fce9b6ab98826..4f19f14e8db9d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -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 {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html index 86f9afacba792..f7646bc024e00 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html @@ -32,15 +32,17 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts index 7fe5abd41b0ce..66059f117145f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts @@ -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 index 0000000000000..e3ee72126f310 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.html @@ -0,0 +1,48 @@ + + {{ action | titlecase }} {{ resource | upperFirst }} + + +
+ + +
+
+
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 index 0000000000000..e69de29bb2d1d 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 index 0000000000000..beda0bd026713 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.spec.ts @@ -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; + 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 index 0000000000000..2b17dba5bde1d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component.ts @@ -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 = OsdPgScrubModalOptions.basicOptions; + + @ViewChild('advancedOptionsValues') + advancedOptionsValues: ConfigOptionComponent; + advancedOptions: Array = 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 index 0000000000000..b7c07d18b985f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-pg-scrub-modal/osd-pg-scrub-modal.options.ts @@ -0,0 +1,39 @@ +export class OsdPgScrubModalOptions { + public static basicOptions: Array = [ + '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 = [ + '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' + ]; +} -- 2.39.5