management.
- **log**: include all features related to Ceph logs management.
- **grafana**: include all features related to Grafana proxy.
+- **dashboard-settings**: allows to change dashboard settings.
A *role* specifies a set of mappings between a *security scope* and a set of
*permissions*. There are four types of permissions:
The list of system roles are:
- **administrator**: provides full permissions for all security scopes.
-- **read-only**: provides *read* permission for all security scopes.
+- **read-only**: provides *read* permission for all security scopes except
+ the dashboard settings.
- **block-manager**: provides full permissions for *rbd-image*,
*rbd-mirroring*, and *iscsi* scopes.
- **rgw-manager**: provides full permissions for the *rgw* scope
--- /dev/null
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+from .helper import DashboardTestCase, JList, JObj, JAny
+from pprint import pformat
+
+
+class SettingsTest(DashboardTestCase):
+ def setUp(self):
+ self.settings = self._get('/api/settings')
+
+ def tearDown(self):
+ self._put(
+ '/api/settings',
+ {setting['name']: setting['value']
+ for setting in self.settings})
+
+ def test_list_settings(self):
+ settings = self._get('/api/settings')
+ self.assertGreater(len(settings), 10)
+ self.assertSchema(
+ settings,
+ JList(
+ JObj({
+ 'default': JAny(none=False),
+ 'name': str,
+ 'type': str,
+ 'value': JAny(none=False)
+ })))
+ self.assertStatus(200)
+
+ def test_get_setting(self):
+ setting = self._get('/api/settings/rgw-api-access-key')
+ self.assertSchema(
+ setting,
+ JObj({
+ 'default': JAny(none=False),
+ 'name': str,
+ 'type': str,
+ 'value': JAny(none=False)
+ }))
+ self.assertStatus(200)
+
+ def test_set_setting(self):
+ self._put('/api/settings/rgw-api-access-key', {'value': 'foo'})
+ self.assertStatus(200)
+
+ value = self._get('/api/settings/rgw-api-access-key')['value']
+ self.assertEqual('foo', value)
+
+ def test_bulk_set(self):
+ self._put('/api/settings', {
+ 'RGW_API_HOST': 'somehost',
+ 'RGW_API_PORT': 7777,
+ })
+ self.assertStatus(200)
+
+ host = self._get('/api/settings/rgw-api-host')['value']
+ self.assertStatus(200)
+ self.assertEqual('somehost', host)
+
+ port = self._get('/api/settings/rgw-api-port')['value']
+ self.assertStatus(200)
+ self.assertEqual(7777, port)
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from contextlib import contextmanager
+
+import cherrypy
+
+
+from . import ApiController, RESTController
+from ..settings import Settings as SettingsModule, Options
+from ..security import Scope
+
+
+@ApiController('/settings', Scope.CONFIG_OPT)
+class Settings(RESTController):
+ """
+ Enables to manage the settings of the dashboard (not the Ceph cluster).
+ """
+ @contextmanager
+ def _attribute_handler(self, name):
+ """
+ :type name: str|dict[str, str]
+ :rtype: str|dict[str, str]
+ """
+ if isinstance(name, dict):
+ result = {self._to_native(key): value
+ for key, value in name.items()}
+ else:
+ result = self._to_native(name)
+
+ try:
+ yield result
+ except AttributeError:
+ raise cherrypy.NotFound(result)
+
+ @staticmethod
+ def _to_native(setting):
+ return setting.upper().replace('-', '_')
+
+ def list(self):
+ return [
+ self._get(name) for name in Options.__dict__
+ if name.isupper() and not name.startswith('_')
+ ]
+
+ def _get(self, name):
+ with self._attribute_handler(name) as sname:
+ default, data_type = getattr(Options, sname)
+ return {
+ 'name': sname,
+ 'default': default,
+ 'type': data_type.__name__,
+ 'value': getattr(SettingsModule, sname)
+ }
+
+ def get(self, name):
+ return self._get(name)
+
+ def set(self, name, value):
+ with self._attribute_handler(name) as sname:
+ setattr(SettingsModule, self._to_native(sname), value)
+
+ def delete(self, name):
+ with self._attribute_handler(name) as sname:
+ delattr(SettingsModule, self._to_native(sname))
+
+ def bulk_set(self, **kwargs):
+ with self._attribute_handler(kwargs) as data:
+ for name, value in data.items():
+ setattr(SettingsModule, self._to_native(name), value)
LOG = "log"
GRAFANA = "grafana"
USER = "user"
+ DASHBOARD_SETTINGS = "dashboard-settings"
@classmethod
def all_scopes(cls):
# read-only role provides read-only permission for all scopes
READ_ONLY_ROLE = Role('read-only', 'Read-Only', dict([
(scope_name, [_P.READ]) for scope_name in Scope.all_scopes()
+ if scope_name != Scope.DASHBOARD_SETTINGS
]))
msg = 'expected body:\n%r\n\nactual body:\n%r' % (
data, json_body)
self._handlewebError(msg)
+
+ def assertInJsonBody(self, data, msg=None):
+ json_body = self.jsonBody()
+ if data not in json_body:
+ if msg is None:
+ msg = 'expected %r to be in %r' % (data, json_body)
+ self._handlewebError(msg)
import errno
import unittest
-
from .. import mgr
from .. import settings
+from ..controllers.settings import Settings as SettingsController
from ..settings import Settings, handle_option_command
+from .helper import ControllerTestCase
class SettingsTest(unittest.TestCase):
self.assertEqual(str(ctx.exception),
"type object 'Options' has no attribute 'NON_EXISTENT_OPTION'")
+
+
+class SettingsControllerTest(ControllerTestCase):
+ config_values = {}
+
+ @classmethod
+ def setup_server(cls):
+ # pylint: disable=protected-access
+
+ SettingsController._cp_config['tools.authenticate.on'] = False
+ cls.setup_controllers([SettingsController])
+
+ @classmethod
+ def mock_set_config(cls, attr, val):
+ cls.config_values[attr] = val
+
+ @classmethod
+ def mock_get_config(cls, attr, default):
+ return cls.config_values.get(attr, default)
+
+ def setUp(self):
+ self.config_values.clear()
+ mgr.set_config.side_effect = self.mock_set_config
+ mgr.get_config.side_effect = self.mock_get_config
+
+ def test_settings_list(self):
+ self._get('/api/settings')
+ data = self.jsonBody()
+ self.assertTrue(len(data) > 0)
+ self.assertStatus(200)
+ self.assertIn('default', data[0].keys())
+ self.assertIn('type', data[0].keys())
+ self.assertIn('name', data[0].keys())
+ self.assertIn('value', data[0].keys())
+
+ def test_rgw_daemon_get(self):
+ self._get('/api/settings/grafana-api-username')
+ self.assertStatus(200)
+ self.assertJsonBody({
+ u'default': u'admin',
+ u'type': u'str',
+ u'name': u'GRAFANA_API_USERNAME',
+ u'value': u'admin',
+ })
+
+ def test_set(self):
+ self._put('/api/settings/GRAFANA_API_USERNAME', {'value': 'foo'},)
+ self.assertStatus(200)
+
+ self._get('/api/settings/GRAFANA_API_USERNAME')
+ self.assertStatus(200)
+ self.assertInJsonBody('default')
+ self.assertInJsonBody('type')
+ self.assertInJsonBody('name')
+ self.assertInJsonBody('value')
+ self.assertEqual(self.jsonBody()['value'], 'foo')
+
+ def test_bulk_set(self):
+ self._put('/api/settings', {
+ 'GRAFANA_API_USERNAME': 'foo',
+ 'GRAFANA_API_HOST': 'somehost',
+ })
+ self.assertStatus(200)
+
+ self._get('/api/settings/grafana-api-username')
+ self.assertStatus(200)
+ body = self.jsonBody()
+ self.assertEqual(body['value'], 'foo')
+
+ self._get('/api/settings/grafana-api-username')
+ self.assertStatus(200)
+ self.assertEqual(self.jsonBody()['value'], 'foo')
+
+ self._get('/api/settings/grafana-api-host')
+ self.assertStatus(200)
+ self.assertEqual(self.jsonBody()['value'], 'somehost')