From: Tatjana Dehler Date: Wed, 13 Mar 2019 13:51:43 +0000 (+0100) Subject: mgr/dashboard: Add config filter and delete routes X-Git-Tag: v15.1.0~2633^2~3 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=cede2d4590429abacee66537e406f2ed2ff4ac0f;p=ceph.git mgr/dashboard: Add config filter and delete routes Add a filter route to the configurations endpoint to get a subset of config options in one request. Add a delete route to the configurations endpoint to delete a specific config option value. The commit contains the frontend and backend related changes. It also adds the missing '/' to `ConfigurationService.bulkCreate` and unit test. Signed-off-by: Tatjana Dehler --- diff --git a/qa/tasks/mgr/dashboard/test_cluster_configuration.py b/qa/tasks/mgr/dashboard/test_cluster_configuration.py index 2a718447c1c6..4112273e6400 100644 --- a/qa/tasks/mgr/dashboard/test_cluster_configuration.py +++ b/qa/tasks/mgr/dashboard/test_cluster_configuration.py @@ -43,6 +43,36 @@ class ClusterConfigurationTest(DashboardTestCase): if orig_value: self._ceph_cmd(['config', 'set', 'mon', config_name, orig_value[0]['value']]) + def test_filter_config_options(self): + config_names = ['osd_scrub_during_recovery', 'osd_scrub_begin_hour', 'osd_scrub_end_hour'] + data = self._get('/api/cluster_conf/filter?names={}'.format(','.join(config_names))) + self.assertStatus(200) + self.assertIsInstance(data, list) + self.assertEqual(len(data), 3) + for conf in data: + self._validate_single(conf) + self.assertIn(conf['name'], config_names) + + def test_filter_config_options_empty_names(self): + self._get('/api/cluster_conf/filter?names=') + self.assertStatus(404) + self.assertEqual(self._resp.json()['detail'], 'Config options `` not found') + + def test_filter_config_options_unknown_name(self): + self._get('/api/cluster_conf/filter?names=abc') + self.assertStatus(404) + self.assertEqual(self._resp.json()['detail'], 'Config options `abc` not found') + + def test_filter_config_options_contains_unknown_name(self): + config_names = ['osd_scrub_during_recovery', 'osd_scrub_begin_hour', 'abc'] + data = self._get('/api/cluster_conf/filter?names={}'.format(','.join(config_names))) + self.assertStatus(200) + self.assertIsInstance(data, list) + self.assertEqual(len(data), 2) + for conf in data: + self._validate_single(conf) + self.assertIn(conf['name'], config_names) + def test_create(self): config_name = 'debug_ms' orig_value = self._get_config_by_name(config_name) @@ -65,6 +95,29 @@ class ClusterConfigurationTest(DashboardTestCase): self._clear_all_values_for_config_option(config_name) self._reset_original_values(config_name, orig_value) + def test_delete(self): + config_name = 'debug_ms' + orig_value = self._get_config_by_name(config_name) + + # set a config option + expected_result = [{'section': 'mon', 'value': '0/3'}] + self._post('/api/cluster_conf', { + 'name': config_name, + 'value': expected_result + }) + self.assertStatus(201) + self._wait_for_expected_get_result(self._get_config_by_name, config_name, expected_result) + + # delete it and check if it's deleted + self._delete('/api/cluster_conf/{}?section={}'.format(config_name, 'mon')) + self.assertStatus(204) + result = self._wait_for_expected_get_result(self._get_config_by_name, config_name, None) + self.assertEqual(result, None) + + # reset original value + self._clear_all_values_for_config_option(config_name) + self._reset_original_values(config_name, orig_value) + def test_create_cant_update_at_runtime(self): config_name = 'public_bind_addr' # not updatable config_value = [{'section': 'global', 'value': 'true'}] diff --git a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py index fcf3f8191454..dc3d1ab90405 100644 --- a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py +++ b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py @@ -38,6 +38,22 @@ class ClusterConfiguration(RESTController): def get(self, name): return self._get_config_option(name) + @RESTController.Collection('GET', query_params=['name']) + def filter(self, names=None): + config_options = [] + + if names: + for name in names.split(','): + try: + config_options.append(self._get_config_option(name)) + except cherrypy.HTTPError: + pass + + if not config_options: + raise cherrypy.HTTPError(404, 'Config options `{}` not found'.format(names)) + + return config_options + def create(self, name, value): # Check if config option is updateable at runtime self._updateable_at_runtime([name]) @@ -57,6 +73,9 @@ class ClusterConfiguration(RESTController): else: CephService.send_command('mon', 'config rm', who=section, name=name) + def delete(self, name, section): + return CephService.send_command('mon', 'config rm', who=section, name=name) + def bulk_set(self, options): self._updateable_at_runtime(options.keys()) 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 index ee3ba222979a..b37362913275 100755 --- 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 @@ -6,14 +6,17 @@ import { RouterTestingModule } from '@angular/router/testing'; import * as _ from 'lodash'; 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 { SharedModule } from '../../../../shared/shared.module'; import { OsdRecvSpeedModalComponent } from './osd-recv-speed-modal.component'; describe('OsdRecvSpeedModalComponent', () => { let component: OsdRecvSpeedModalComponent; let fixture: ComponentFixture; + let configurationService: ConfigurationService; configureTestBed({ imports: [ @@ -34,7 +37,7 @@ describe('OsdRecvSpeedModalComponent', () => { fixture = TestBed.createComponent(OsdRecvSpeedModalComponent); component = fixture.componentInstance; fixture.detectChanges(); - + configurationService = TestBed.get(ConfigurationService); configOptions = [ { name: 'osd_max_backfills', @@ -61,12 +64,44 @@ describe('OsdRecvSpeedModalComponent', () => { default: 0 } ]; + spyOn(configurationService, 'filter').and.returnValue(observableOf(configOptions)); }); it('should create', () => { expect(component).toBeTruthy(); }); + describe('ngOnInit', () => { + let setPriority; + let setValidators; + + beforeEach(() => { + setPriority = spyOn(component, 'setPriority').and.callThrough(); + setValidators = spyOn(component, 'setValidators').and.callThrough(); + component.ngOnInit(); + }); + + it('should call setValidators', () => { + expect(setValidators).toHaveBeenCalled(); + }); + + it('should get and set priority correctly', () => { + const defaultPriority = _.find(component.priorities, (p) => { + return _.isEqual(p.name, 'default'); + }); + expect(setPriority).toHaveBeenCalledWith(defaultPriority); + }); + + it('should set descriptions correctly', () => { + expect(component.priorityAttrs['osd_max_backfills'].desc).toBe(''); + expect(component.priorityAttrs['osd_recovery_max_active'].desc).toBe(''); + expect(component.priorityAttrs['osd_recovery_max_single_start'].desc).toBe(''); + expect(component.priorityAttrs['osd_recovery_sleep'].desc).toBe( + 'Time in seconds to sleep before next recovery or backfill op' + ); + }); + }); + describe('setPriority', () => { it('should prepare the form for a custom priority', () => { const customPriority = { 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 index dcb7db6985f4..17e98894938f 100755 --- 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 @@ -4,8 +4,6 @@ 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 { OsdService } from '../../../../shared/api/osd.service'; @@ -76,24 +74,14 @@ export class OsdRecvSpeedModalComponent implements OnInit { } ngOnInit() { - const observables = []; - Object.keys(this.priorityAttrs).forEach((configOptionName) => { - observables.push(this.configService.get(configOptionName)); - }); - - observableForkJoin(observables) - .pipe( - mergeMap((configOptions) => { - return of(this.getCurrentValues(configOptions)); - }) - ) - .subscribe((resp) => { - this.detectPriority(resp.values, (priority) => { - this.setPriority(priority); - }); - this.setDescription(resp.configOptions); - this.setValidators(resp.configOptions); + this.configService.filter(Object.keys(this.priorityAttrs)).subscribe((data: any) => { + const config_option_values = this.getCurrentValues(data); + this.detectPriority(config_option_values.values, (priority) => { + this.setPriority(priority); }); + this.setDescription(config_option_values.configOptions); + this.setValidators(config_option_values.configOptions); + }); } detectPriority(configOptionValues: any, callbackFn: Function) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.spec.ts index 0b4133e5a409..9add3a5b7745 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.spec.ts @@ -51,4 +51,30 @@ describe('ConfigurationService', () => { expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(configOption); }); + + it('should call bulkCreate', () => { + const configOptions = { + configOption1: { section: 'section', value: 'value' }, + configOption2: { section: 'section', value: 'value' } + }; + service.bulkCreate(configOptions).subscribe(); + const req = httpTesting.expectOne('api/cluster_conf/'); + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(configOptions); + }); + + it('should call filter', () => { + const configOptions = ['configOption1', 'configOption2', 'configOption3']; + service.filter(configOptions).subscribe(); + const req = httpTesting.expectOne( + 'api/cluster_conf/filter?names=configOption1,configOption2,configOption3' + ); + expect(req.request.method).toBe('GET'); + }); + + it('should call delete', () => { + service.delete('testOption', 'testSection').subscribe(); + const reg = httpTesting.expectOne('api/cluster_conf/testOption?section=testSection'); + expect(reg.request.method).toBe('DELETE'); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts index a977a17d180e..3355b8fb7723 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts @@ -18,11 +18,19 @@ export class ConfigurationService { return this.http.get(`api/cluster_conf/${configOption}`); } + filter(configOptionNames: Array) { + return this.http.get(`api/cluster_conf/filter?names=${configOptionNames.join(',')}`); + } + create(configOption: ConfigFormCreateRequestModel) { return this.http.post('api/cluster_conf/', configOption); } + delete(configOption: string, section: string) { + return this.http.delete(`api/cluster_conf/${configOption}?section=${section}`); + } + bulkCreate(configOptions: Object) { - return this.http.put('api/cluster_conf', configOptions); + return this.http.put('api/cluster_conf/', configOptions); } }