From: Tatjana Dehler Date: Wed, 23 May 2018 14:35:51 +0000 (+0200) Subject: mgr/dashboard: add config options to documentation page X-Git-Tag: v14.0.1~1118^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=9ebf5eb6506da6f7b1cd97aac3cfb9d8d711fd96;p=ceph.git mgr/dashboard: add config options to documentation page This commit adds the config options stored by the MON database to the configuration documentation page. One can filter for these config options by setting the 'Source' filter to 'mon' on the configuration documentation page. 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 6cfe605e9474d..d0e8c450c5694 100644 --- a/qa/tasks/mgr/dashboard/test_cluster_configuration.py +++ b/qa/tasks/mgr/dashboard/test_cluster_configuration.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import time + from .helper import DashboardTestCase, authenticate @@ -23,6 +25,22 @@ class ClusterConfigurationTest(DashboardTestCase): data = self._get('/api/cluster_conf/fantasy_name') self.assertStatus(404) + @authenticate + def test_get_specific_db_config_option(self): + def _get_mon_allow_pool_delete_config(): + data = self._get('/api/cluster_conf/mon_allow_pool_delete') + return data['value'][0] + + self._ceph_cmd(['config', 'set', 'mon', 'mon_allow_pool_delete', 'true']) + result = self._wait_for_expected_get_result(_get_mon_allow_pool_delete_config, + {'section': 'mon', 'value': 'true'}) + self.assertEqual(result, {'section': 'mon', 'value': 'true'}) + + self._ceph_cmd(['config', 'set', 'mon', 'mon_allow_pool_delete', 'false']) + result = self._wait_for_expected_get_result(_get_mon_allow_pool_delete_config, + {'section': 'mon', 'value': 'false'}) + self.assertEqual(result, {'section': 'mon', 'value': 'false'}) + def _validate_single(self, data): self.assertIn('name', data) self.assertIn('daemon_default', data) @@ -37,3 +55,23 @@ class ClusterConfigurationTest(DashboardTestCase): self.assertIn('type', data) self.assertIn('desc', data) + if 'value' in data: + self.assertIn('source', data) + self.assertIsInstance(data['value'], list) + + for entry in data['value']: + self.assertIsInstance(entry, dict) + self.assertIn('section', entry) + self.assertIn('value', entry) + + def _wait_for_expected_get_result(self, get_func, expected_result, max_attempts=30, + sleep_time=1): + attempts = 0 + while attempts < max_attempts: + get_result = get_func() + if get_result == expected_result: + self.assertStatus(200) + return get_result + + time.sleep(sleep_time) + attempts += 1 diff --git a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py index 35b5ca3778f44..0e724cf1a4acc 100644 --- a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py +++ b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py @@ -4,19 +4,39 @@ from __future__ import absolute_import import cherrypy from .. import mgr +from ..services.ceph_service import CephService from . import ApiController, RESTController, AuthRequired @ApiController('/cluster_conf') @AuthRequired() class ClusterConfiguration(RESTController): + + def _append_config_option_values(self, options): + """ + Appends values from the config database (if available) to the given options + :param options: list of config options + :return: list of config options extended by their current values + """ + config_dump = CephService.send_command('mon', 'config dump') + for config_dump_entry in config_dump: + for i, elem in enumerate(options): + if config_dump_entry['name'] == elem['name']: + if 'value' not in elem: + options[i]['value'] = [] + options[i]['source'] = 'mon' + + options[i]['value'].append({'section': config_dump_entry['section'], + 'value': config_dump_entry['value']}) + return options + def list(self): options = mgr.get("config_options")['options'] - return options + return self._append_config_option_values(options) def get(self, name): for option in mgr.get('config_options')['options']: if option['name'] == name: - return option + return self._append_config_option_values([option])[0] raise cherrypy.HTTPError(404) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html index 9299ffac05d96..9e7c64082b446 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html @@ -28,4 +28,11 @@ + + + + {{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}
+
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts index 8acc8046a4430..ef06274935b52 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ConfigurationService } from '../../../shared/api/configuration.service'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; @@ -9,11 +9,10 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection'; templateUrl: './configuration.component.html', styleUrls: ['./configuration.component.scss'] }) -export class ConfigurationComponent { +export class ConfigurationComponent implements OnInit { data = []; columns: CdTableColumn[]; selection = new CdTableSelection(); - filters = [ { label: 'Level', @@ -44,19 +43,46 @@ export class ConfigurationComponent { return row.services.includes(value); } + }, + { + label: 'Source', + prop: 'source', + value: 'any', + options: ['any', 'mon'], + applyFilter: (row, value) => { + if (value === 'any') { + return true; + } + + if (!row.hasOwnProperty('source')) { + return false; + } + + return row.source.includes(value); + } } ]; - constructor( - private configurationService: ConfigurationService, - ) { + @ViewChild('confValTpl') public confValTpl: TemplateRef; + + constructor(private configurationService: ConfigurationService) {} + + ngOnInit() { this.columns = [ { flexGrow: 2, canAutoResize: true, prop: 'name' }, + { + flexGrow: 2, + prop: 'value', + name: 'Current value', + cellClass: 'wrap', + cellTemplate: this.confValTpl + }, + { flexGrow: 1, prop: 'source' }, { flexGrow: 2, prop: 'desc', name: 'Description', cellClass: 'wrap' }, { flexGrow: 2, prop: 'long_desc', name: 'Long description', cellClass: 'wrap' }, { flexGrow: 1, prop: 'type' }, { flexGrow: 1, prop: 'level' }, - { flexGrow: 1, prop: 'default', cellClass: 'wrap'}, + { flexGrow: 1, prop: 'default', cellClass: 'wrap' }, { flexGrow: 2, prop: 'daemon_default', name: 'Daemon default' }, { flexGrow: 1, prop: 'tags', name: 'Tags' }, { flexGrow: 1, prop: 'services', name: 'Services' },