]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: added settings module
authorRicardo Dias <rdias@suse.com>
Mon, 29 Jan 2018 16:10:50 +0000 (16:10 +0000)
committerRicardo Dias <rdias@suse.com>
Wed, 7 Mar 2018 16:29:54 +0000 (16:29 +0000)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/pybind/mgr/dashboard_v2/module.py
src/pybind/mgr/dashboard_v2/settings.py [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/tests/test_settings.py [new file with mode: 0644]

index 502e2c5d1002d0245fa8987be60c8e39bc6d758e..6e2ea8aaf05fb3d220f4f59fe32ab1f5bc619c7b 100644 (file)
@@ -24,6 +24,7 @@ from . import logger, mgr
 from .controllers.auth import Auth
 from .tools import load_controllers, json_error_page, SessionExpireAtBrowserCloseTool, \
                    NotificationQueue
+from .settings import options_command_list, handle_option_command
 
 
 # cherrypy likes to sys.exit on error.  don't let it take us down too!
@@ -64,6 +65,7 @@ class Module(MgrModule):
             'perm': 'w'
         }
     ]
+    COMMANDS.extend(options_command_list())
 
     @property
     def url_prefix(self):
@@ -144,6 +146,9 @@ class Module(MgrModule):
         logger.info('Stopped server')
 
     def handle_command(self, cmd):
+        res = handle_option_command(cmd)
+        if res[0] != -errno.ENOSYS:
+            return res
         if cmd['prefix'] == 'dashboard set-login-credentials':
             Auth.set_login_credentials(cmd['username'], cmd['password'])
             return 0, 'Username and password updated', ''
diff --git a/src/pybind/mgr/dashboard_v2/settings.py b/src/pybind/mgr/dashboard_v2/settings.py
new file mode 100644 (file)
index 0000000..4f68fbb
--- /dev/null
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import errno
+import inspect
+from six import add_metaclass
+
+from . import mgr
+
+
+class Options(object):
+    """
+    If you need to store some configuration value please add the config option
+    name as a class attribute to this class.
+
+    Example::
+
+        GRAFANA_API_HOST = ('localhost', str)
+        GRAFANA_API_PORT = (3000, int)
+    """
+    pass
+
+
+class SettingsMeta(type):
+    def __getattr__(cls, attr):
+        default, stype = getattr(Options, attr)
+        return stype(mgr.get_config(attr, default))
+
+    def __setattr__(cls, attr, value):
+        if not attr.startswith('_') and hasattr(Options, attr):
+            mgr.set_config(attr, str(value))
+        else:
+            setattr(SettingsMeta, attr, value)
+
+
+# pylint: disable=no-init
+@add_metaclass(SettingsMeta)
+class Settings(object):
+    pass
+
+
+def _options_command_map():
+    def filter_attr(member):
+        return not inspect.isroutine(member)
+
+    cmd_map = {}
+    for option, value in inspect.getmembers(Options, filter_attr):
+        if option.startswith('_'):
+            continue
+        key_get = 'dashboard get-{}'.format(option.lower().replace('_', '-'))
+        key_set = 'dashboard set-{}'.format(option.lower().replace('_', '-'))
+        cmd_map[key_get] = {'name': option, 'type': None}
+        cmd_map[key_set] = {'name': option, 'type': value[1]}
+    return cmd_map
+
+
+_OPTIONS_COMMAND_MAP = _options_command_map()
+
+
+def options_command_list():
+    """
+    This function generates a list of ``get`` and ``set`` commands
+    for each declared configuration option in class ``Options``.
+    """
+    def py2ceph(pytype):
+        if pytype == str:
+            return 'CephString'
+        elif pytype == int:
+            return 'CephInt'
+        return 'CephString'
+
+    cmd_list = []
+    for cmd, opt in _OPTIONS_COMMAND_MAP.items():
+        if not opt['type']:
+            cmd_list.append({
+                'cmd': '{}'.format(cmd),
+                'desc': 'Get the {} option value'.format(opt['name']),
+                'perm': 'r'
+            })
+        else:
+            cmd_list.append({
+                'cmd': '{} name=value,type={}'
+                       .format(cmd, py2ceph(opt['type'])),
+                'desc': 'Set the {} option value'.format(opt['name']),
+                'perm': 'w'
+            })
+
+    return cmd_list
+
+
+def handle_option_command(cmd):
+    if cmd['prefix'] not in _OPTIONS_COMMAND_MAP:
+        return (-errno.ENOSYS, '', "Command not found '{}'".format(cmd['prefix']))
+
+    opt = _OPTIONS_COMMAND_MAP[cmd['prefix']]
+    if not opt['type']:
+        # get option
+        return 0, str(getattr(Settings, opt['name'])), ''
+
+    # set option
+    setattr(Settings, opt['name'], opt['type'](cmd['value']))
+    return 0, 'Option {} updated'.format(opt['name']), ''
diff --git a/src/pybind/mgr/dashboard_v2/tests/test_settings.py b/src/pybind/mgr/dashboard_v2/tests/test_settings.py
new file mode 100644 (file)
index 0000000..92fcf7f
--- /dev/null
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import errno
+import unittest
+
+from .. import mgr
+from .. import settings
+from ..settings import Settings, handle_option_command
+
+
+class SettingsTest(unittest.TestCase):
+    CONFIG_KEY_DICT = {}
+
+    @classmethod
+    def setUpClass(cls):
+        # pylint: disable=protected-access
+        settings.Options.GRAFANA_API_HOST = ('localhost', str)
+        settings.Options.GRAFANA_API_PORT = (3000, int)
+        settings._OPTIONS_COMMAND_MAP = settings._options_command_map()
+
+    @classmethod
+    def mock_set_config(cls, attr, val):
+        cls.CONFIG_KEY_DICT[attr] = val
+
+    @classmethod
+    def mock_get_config(cls, attr, default):
+        return cls.CONFIG_KEY_DICT.get(attr, default)
+
+    def setUp(self):
+        self.CONFIG_KEY_DICT.clear()
+        mgr.set_config.side_effect = self.mock_set_config
+        mgr.get_config.side_effect = self.mock_get_config
+        if Settings.GRAFANA_API_HOST != 'localhost':
+            Settings.GRAFANA_API_HOST = 'localhost'
+        if Settings.GRAFANA_API_PORT != 3000:
+            Settings.GRAFANA_API_PORT = 3000
+
+    def test_get_setting(self):
+        self.assertEqual(Settings.GRAFANA_API_HOST, 'localhost')
+
+    def test_set_setting(self):
+        Settings.GRAFANA_API_HOST = 'grafanahost'
+        self.assertEqual(Settings.GRAFANA_API_HOST, 'grafanahost')
+
+    def test_get_cmd(self):
+        r, out, err = handle_option_command(
+            {'prefix': 'dashboard get-grafana-api-port'})
+        self.assertEqual(r, 0)
+        self.assertEqual(out, '3000')
+        self.assertEqual(err, '')
+
+    def test_set_cmd(self):
+        r, out, err = handle_option_command(
+            {'prefix': 'dashboard set-grafana-api-port',
+             'value': '4000'})
+        self.assertEqual(r, 0)
+        self.assertEqual(out, 'Option GRAFANA_API_PORT updated')
+        self.assertEqual(err, '')
+
+    def test_inv_cmd(self):
+        r, out, err = handle_option_command(
+            {'prefix': 'dashboard get-non-existent-option'})
+        self.assertEqual(r, -errno.ENOSYS)
+        self.assertEqual(out, '')
+        self.assertEqual(err, "Command not found "
+                              "'dashboard get-non-existent-option'")
+
+    def test_sync(self):
+        Settings.GRAFANA_API_PORT = 5000
+        r, out, err = handle_option_command(
+            {'prefix': 'dashboard get-grafana-api-port'})
+        self.assertEqual(r, 0)
+        self.assertEqual(out, '5000')
+        self.assertEqual(err, '')
+        r, out, err = handle_option_command(
+            {'prefix': 'dashboard set-grafana-api-host',
+             'value': 'new-local-host'})
+        self.assertEqual(r, 0)
+        self.assertEqual(out, 'Option GRAFANA_API_HOST updated')
+        self.assertEqual(err, '')
+        self.assertEqual(Settings.GRAFANA_API_HOST, 'new-local-host')
+
+    def test_attribute_error(self):
+        with self.assertRaises(AttributeError) as ctx:
+            _ = Settings.NON_EXISTENT_OPTION
+
+        self.assertEqual(str(ctx.exception),
+                         "type object 'Options' has no attribute 'NON_EXISTENT_OPTION'")