# -*- 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, Proxy, Endpoint, ReadPermission
-from .. import logger
+from . import ApiController, BaseController, Endpoint, ReadPermission
from ..security import Scope
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')}
+ def url_validation(self, method, path):
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)
+ path)
- 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, ''
+ return response.status_code
@ApiController('/grafana', Scope.GRAFANA)
@ReadPermission
def url(self):
response = {'instance': Settings.GRAFANA_API_URL}
-
return response
-
-@ApiController('/grafana/proxy', Scope.GRAFANA)
-class GrafanaProxy(BaseController):
- @Proxy()
+ @Endpoint()
@ReadPermission
- 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
+ def validation(self, params):
+ grafana = GrafanaRestClient()
+ method = 'GET'
+ url = Settings.GRAFANA_API_URL + '/api/dashboards/uid/' + params
+ response = grafana.url_validation(method, url)
+ return response
<tab i18n-heading
*ngIf="grafanaPermission.read"
heading="Performance Details">
- <cd-grafana [grafanaPath]="'rRfFzWtik/mds-performance?var-mds_servers=mds.' + grafanaId"
+ <cd-grafana [grafanaPath]="'mds-performance?var-mds_servers=mds.' + grafanaId"
+ uid="rRfFzWtik"
grafanaStyle="one">
</cd-grafana>
</tab>
<tabset *ngIf="selection.hasSingleSelection && grafanaPermission.read">
<tab i18n-heading
heading="Performance Details">
- <cd-grafana [grafanaPath]="'7IGu2Ttmz/host-details?'"
+ <cd-grafana [grafanaPath]="'host-details?'"
+ uid="7IGu2Ttmz"
grafanaStyle="three">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="permissions.grafana.read"
heading="Overall Performance">
- <cd-grafana [grafanaPath]="'lxnjcTAmk/host-overview?'"
+ <cd-grafana [grafanaPath]="'host-overview?'"
+ uid="lxnjcTAmk"
grafanaStyle="two">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="grafanaPermission.read"
heading="Performance Details">
- <cd-grafana [grafanaPath]="'MKj_9ipiz/osd-device-details?var-osd_id=' + osd['id']"
+ <cd-grafana [grafanaPath]="'osd-device-details?var-osd_id=' + osd['id']"
+ uid="MKj_9ipiz"
grafanaStyle="GrafanaStyles.two">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="permissions.grafana.read"
heading="Overall Performance">
- <cd-grafana [grafanaPath]="'lo02I1Aiz/osd-overview?'"
+ <cd-grafana [grafanaPath]="'osd-overview?'"
+ uid="lo02I1Aiz"
grafanaStyle="three">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="permissions.grafana.read"
heading="Performance Details">
- <cd-grafana [grafanaPath]="'8ypfkWpik/ceph-pool-detail?var-pool_name='
+ <cd-grafana [grafanaPath]="'ceph-pool-detail?var-pool_name='
+ selection.first()['pool_name']"
+ uid="8ypfkWpik"
grafanaStyle="one">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="permissions.grafana.read"
heading="Overall Performance">
- <cd-grafana [grafanaPath]="'z99hzWtmk/ceph-pools-overview?'"
+ <cd-grafana [grafanaPath]="'ceph-pools-overview?'"
+ uid="z99hzWtmk"
grafanaStyle="two">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="grafanaPermission.read"
heading="Performance Details">
- <cd-grafana [grafanaPath]="'x5ARzZtmk/rgw-instance-detail?var-rgw_servers=rgw.' + this.selection.first().id"
+ <cd-grafana [grafanaPath]="'rgw-instance-detail?var-rgw_servers=rgw.' + this.selection.first().id"
+ uid="x5ARzZtmk"
grafanaStyle="one">
</cd-grafana>
</tab>
<tab i18n-heading
*ngIf="grafanaPermission.read"
heading="Overall Performance">
- <cd-grafana [grafanaPath]="'WAkugZpiz/rgw-overview?'"
+ <cd-grafana [grafanaPath]="'rgw-overview?'"
+ uid="WAkugZpiz"
grafanaStyle="two">
</cd-grafana>
</tab>
getLogs() {
return this.http.get('api/logs/all');
}
+
+ validateDashboardUrl(uid) {
+ return this.http.get(`api/grafana/validation/${uid}`);
+ }
}
expect(service).toBeTruthy();
});
+ it('should call validateGrafanaDashboardUrl', () => {
+ service.validateGrafanaDashboardUrl('s').subscribe();
+ const req = httpTesting.expectOne('api/grafana/validation/s');
+ expect(req.request.method).toBe('GET');
+ });
+
describe('getSettingsValue', () => {
const testMethod = (data, expected: string) => {
expect(service['getSettingsValue'](data)).toBe(expected);
private getSettingsValue(data: any): string {
return data.value || data.instance || '';
}
+
+ validateGrafanaDashboardUrl(uid) {
+ return this.http.get(`api/grafana/validation/${uid}`);
+ }
}
<a href="{{ docsUrl }}" target="_blank">documentation</a> on how to
configure and enable the monitoring functionality.</cd-info-panel>
+<cd-info-panel *ngIf="!dashboardExist"
+ i18n>Grafana Dashboard doesn't exist. Please refer to
+ <a href="{{ docsUrl }}" target="_blank">documentation</a> on how to
+ add dashboards to Grafana.</cd-info-panel>
+
<div class="row"
- *ngIf="grafanaExist">
+ *ngIf="grafanaExist && dashboardExist">
<div class="col-md-12">
<div dropdown>
<button dropdownToggle
expect(component.grafanaExist).toBe(true);
expect(component.baseUrl).toBe('http:localhost:3000/d/');
expect(component.loading).toBe(false);
- expect(component.url).toBe('http:localhost:3000/d/somePath&refresh=2s&kiosk');
+ component.uid = 'uid';
+ component.getFrame();
+ expect(component.url).toBe('http:localhost:3000/d/uid/somePath&refresh=2s&kiosk');
expect(component.grafanaSrc).toEqual({
- changingThisBreaksApplicationSecurity: 'http:localhost:3000/d/somePath&refresh=2s&kiosk'
+ changingThisBreaksApplicationSecurity: 'http:localhost:3000/d/uid/somePath&refresh=2s&kiosk'
});
});
+
+ it('should have Dashboard', () => {
+ TestBed.get(SettingsService).validateGrafanaDashboardUrl = { uid: 200 };
+ expect(component.dashboardExist).toBe(true);
+ });
});
});
url: string;
protocol: string;
host: string;
- dashboardPath: string;
port: number;
baseUrl: any;
panelStyle: any;
modeText = 'Change time selection';
loading = true;
styles = {};
+ dashboardExist = true;
@Input()
grafanaPath: string;
@Input()
grafanaStyle: string;
+ @Input()
+ uid: string;
+
docsUrl: string;
constructor(
}
getFrame() {
- this.url = this.baseUrl + this.grafanaPath + '&refresh=2s' + this.mode;
+ this.settingsService
+ .validateGrafanaDashboardUrl(this.uid)
+ .subscribe((data: any) => (this.dashboardExist = data === 200));
+ this.url = this.baseUrl + this.uid + '/' + this.grafanaPath + '&refresh=2s' + this.mode;
this.grafanaSrc = this.sanitizer.bypassSecurityTrustResourceUrl(this.url);
}
<context context-type="sourcefile">app/shared/components/grafana/grafana.component.html</context>
<context context-type="linenumber">6</context>
</context-group>
+ </trans-unit><trans-unit id="160a9e80dc089792df42e2400b1e81b9e7025aa7" datatype="html">
+ <source>Grafana Dashboard doesn't exist. Please refer to
+ <x id="START_LINK" ctype="x-a" equiv-text="<a>"/>documentation<x id="CLOSE_LINK" ctype="x-a" equiv-text="</a>"/> on how to
+ add dashboards to Grafana.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/shared/components/grafana/grafana.component.html</context>
+ <context context-type="linenumber">11</context>
+ </context-group>
</trans-unit><trans-unit id="012741ee52b3c050e4a977c37cc2334f7974f141" datatype="html">
<source>Failed to load data.</source>
<context-group purpose="location">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/pool/pool-list/pool-list.component.html</context>
- <context context-type="linenumber">39</context>
+ <context context-type="linenumber">40</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html</context>
<x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/><x id="INTERPOLATION_1" equiv-text="{{ markActionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> if you proceed.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
- <context context-type="linenumber">68</context>
+ <context context-type="linenumber">69</context>
</context-group>
</trans-unit><trans-unit id="2d3a73f6440a7d896d74356fe0a725d731e71cbb" datatype="html">
<source>The OSD is not safe to destroy!</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
- <context context-type="linenumber">77</context>
+ <context context-type="linenumber">78</context>
</context-group>
</trans-unit><trans-unit id="9d08116242443953ebbfe10bc2092e0a694b4adf" datatype="html">
<source><x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/>OSD <x id="INTERPOLATION" equiv-text="{{ selection.first().id }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> will be
<x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/><x id="INTERPOLATION_1" equiv-text="{{ actionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> if you proceed.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/cluster/osd/osd-list/osd-list.component.html</context>
- <context context-type="linenumber">79</context>
+ <context context-type="linenumber">80</context>
</context-group>
</trans-unit><trans-unit id="d2bcd3296d2850de762fb943060b7e086a893181" datatype="html">
<source>Health</source>
-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
+from ..controllers.grafana import Grafana
+from .. import mgr
-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', secure=False)
-class GrafanaMockInstance(BaseController):
- @Proxy()
- def __call__(self, path, **params):
- cherrypy.response.headers['foo'] = 'bar'
- return 'Static Content at path {}'.format(path)
-
-
-class GrafanaControllerTestCase(ControllerTestCase):
+class GrafanaTest(ControllerTestCase):
@classmethod
def setup_server(cls):
settings = {
- 'GRAFANA_API_URL': 'http://localhost:{}/grafana/mocked/'.format(54583),
- 'GRAFANA_API_USERNAME': 'admin',
- 'GRAFANA_API_PASSWORD': 'admin'
+ 'GRAFANA_API_URL': 'http://localhost:3000'
}
mgr.get_module_option.side_effect = settings.get
- GrafanaProxy._cp_config['tools.authenticate.on'] = False # pylint: disable=protected-access
+ # pylint: disable=protected-access
+ Grafana._cp_config['tools.authenticate.on'] = False
+ cls.setup_controllers([Grafana])
- cls.setup_controllers([GrafanaProxy, GrafanaMockInstance])
-
- def test_grafana_proxy(self):
- self._get('/grafana/mocked/foo')
+ def test_url(self):
+ self._get('/api/grafana/url')
self.assertStatus(200)
- self.assertBody('Static Content at path foo')
+ self.assertJsonBody({'instance': 'http://localhost:3000'})
- # Test the proxy
- self._get('/api/grafana/proxy/bar')
- self.assertStatus(200)
- self.assertBody('Static Content at path bar')
+ def test_validation(self):
+ self._get('/api/grafana/validation/foo')
+ self.assertStatus(500)