$ ceph dashboard set-rgw-api-admin-resource <admin_resource>
$ ceph dashboard set-rgw-api-user-id <user_id>
+Enabling the Embedding of Grafana Dashboards
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Grafana and Prometheus are likely going to be bundled and installed by some
+orchestration tools along Ceph in the near future, but currently, you will have
+to install and configure both manually. After you have installed Prometheus and
+Grafana on your preferred hosts, proceed with the following steps::
+
+ 1. Enable the Ceph Exporter which comes as Ceph Manager module by running::
+
+ $ ceph mgr module enable prometheus
+
+ More details can be found on the `documentation
+ <http://docs.ceph.com/docs/master/mgr/prometheus/>`_ of the prometheus
+ module.
+
+ 2. Add the corresponding scrape configuration to Prometheus. This may look
+ like::
+
+ global:
+ scrape_interval: 5s
+
+ scrape_configs:
+ - job_name: 'prometheus'
+ static_configs:
+ - targets: ['localhost:9090']
+ - job_name: 'ceph'
+ static_configs:
+ - targets: ['localhost:9283']
+ - job_name: 'node-exporter'
+ static_configs:
+ - targets: ['localhost:9100']
+
+ 3. Add Prometheus as data source to Grafana
+
+ 4. Install the `vonage-status-panel` plugin using::
+
+ grafana-cli plugins install vonage-status-panel
+
+ 4. Add the Dashboards to Grafana by importing them
+
+ 5. Configure Grafana in `/etc/grafana/grafana.ini` to adapt the URLs to the
+ Ceph Dashboard properly::
+
+ root_url = http://localhost:3000/api/grafana/proxy
+
+After you have configured Grafana and Prometheus, you will need to tell the
+Ceph Manager Dashboard where it can access Grafana and what the credentials are
+to do so. This can be done by using the following commands::
+
+ $ ceph dashboard set-grafana-api-url <url> # default: 'http://localhost:3000'
+ $ ceph dashboard set-grafana-api-username <username> # default: 'admin'
+ $ ceph dashboard set-grafana-api-password <password> # default: 'admin'
+
Accessing the dashboard
^^^^^^^^^^^^^^^^^^^^^^^
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import cherrypy
+import requests
+from six.moves.urllib.parse import urlparse # pylint: disable=import-error
+
+from . import ApiController, BaseController, AuthRequired, Proxy, Endpoint
+from .. import logger
+from ..settings import Settings
+
+
+class GrafanaRestClient(object):
+ _instance = None
+
+ @staticmethod
+ def _raise_for_validation(url, user, password):
+ msg = 'No {} found or misconfigured, please consult the ' \
+ 'documentation about how to configure Grafana for the dashboard.'
+
+ o = urlparse(url)
+ if not (o.netloc and o.scheme):
+ raise LookupError(msg.format('URL'))
+
+ if not all((user, password)):
+ raise LookupError(msg.format('credentials'))
+
+ def __init__(self, url, username, password):
+ """
+ :type url: str
+ :type username: str
+ :type password: str
+ """
+ self._raise_for_validation(url, username, password)
+
+ self._url = url.rstrip('/')
+ self._user = username
+ self._password = password
+
+ @classmethod
+ def instance(cls):
+ """
+ This method shall be used by default to create an instance and will use
+ the settings to retrieve the required credentials.
+
+ :rtype: GrafanaRestClient
+ """
+ if not cls._instance:
+ url = Settings.GRAFANA_API_URL
+ user = Settings.GRAFANA_API_USERNAME
+ password = Settings.GRAFANA_API_PASSWORD
+
+ cls._instance = GrafanaRestClient(url, user, password)
+
+ return cls._instance
+
+ def proxy_request(self, method, path, params, data):
+ url = '{}/{}'.format(self._url, path.lstrip('/'))
+
+ # Forwards some headers
+ headers = {k: v for k, v in cherrypy.request.headers.items()
+ if k.lower() in ('content-type', 'accept')}
+
+ response = requests.request(
+ method,
+ url,
+ params=params,
+ data=data,
+ headers=headers,
+ auth=(self._user, self._password))
+ logger.debug("proxying method=%s path=%s params=%s data=%s", method,
+ path, params, data)
+
+ return response
+
+ def is_service_online(self):
+ try:
+ response = self.instance().proxy_request('GET', '/', None, None)
+ response.raise_for_status()
+ except Exception as e: # pylint: disable=broad-except
+ logger.error(e)
+ return False, str(e)
+
+ return True, ''
+
+
+@ApiController('/grafana')
+@AuthRequired()
+class Grafana(BaseController):
+
+ @Endpoint()
+ def status(self):
+ grafana = GrafanaRestClient.instance()
+ available, msg = grafana.is_service_online()
+ response = {'available': available}
+ if msg:
+ response['message'] = msg
+
+ return response
+
+
+@ApiController('/grafana/proxy')
+@AuthRequired()
+class GrafanaProxy(BaseController):
+ @Proxy()
+ def __call__(self, path, **params):
+ grafana = GrafanaRestClient.instance()
+ method = cherrypy.request.method
+
+ data = None
+ if cherrypy.request.body.length:
+ data = cherrypy.request.body.read()
+
+ response = grafana.proxy_request(method, path, params, data)
+
+ cherrypy.response.headers['Content-Type'] = response.headers[
+ 'Content-Type']
+
+ return response.content
RGW_API_SCHEME = ('http', str)
RGW_API_USER_ID = ('', str)
+ # Grafana settings
+ GRAFANA_API_URL = ('http://localhost:3000', str)
+ GRAFANA_API_USERNAME = ('admin', str)
+ GRAFANA_API_PASSWORD = ('admin', str)
+
@staticmethod
def has_default_value(name):
return getattr(Settings, name, None) is None or \
--- /dev/null
+from unittest import TestCase
+
+import cherrypy
+import six
+from .. import mgr
+from ..controllers import BaseController, Controller, Proxy
+from ..controllers.grafana import GrafanaProxy, GrafanaRestClient
+
+from .helper import ControllerTestCase
+
+
+class Grafana(TestCase):
+ def test_missing_credentials(self):
+ with six.assertRaisesRegex(self, LookupError, r'^No credentials.*'):
+ GrafanaRestClient(
+ url='http://localhost:3000', username='', password='admin')
+
+ with six.assertRaisesRegex(self, LookupError, r'^No URL.*'):
+ GrafanaRestClient(
+ url='//localhost:3000', username='admin', password='admin')
+
+
+@Controller('/grafana/mocked')
+class GrafanaMockInstance(BaseController):
+ @Proxy()
+ def __call__(self, path, **params):
+ cherrypy.response.headers['foo'] = 'bar'
+ return 'Static Content at path {}'.format(path)
+
+
+class GrafanaControllerTestCase(ControllerTestCase):
+ @classmethod
+ def setup_server(cls):
+ settings = {
+ 'GRAFANA_API_URL':
+ 'http://localhost:{}/grafana/mocked/'.format(54583),
+ 'GRAFANA_API_USERNAME': 'admin',
+ 'GRAFANA_API_PASSWORD': 'admin',
+ }
+ mgr.get_config.side_effect = settings.get
+ GrafanaProxy._cp_config['tools.authenticate.on'] = False # pylint: disable=protected-access
+
+ cls.setup_controllers([GrafanaProxy, GrafanaMockInstance])
+
+ def test_grafana_proxy(self):
+ self._get('/grafana/mocked/foo')
+ self.assertStatus(200)
+ self.assertBody('Static Content at path foo')
+
+ # Test the proxy
+ self._get('/api/grafana/proxy/bar')
+ self.assertStatus(200)
+ self.assertBody('Static Content at path bar')