]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: check for existence of Grafana dashboard
authorKanika Murarka <kmurarka@redhat.com>
Wed, 5 Dec 2018 10:33:53 +0000 (16:03 +0530)
committerKanika Murarka <kmurarka@redhat.com>
Fri, 4 Jan 2019 11:04:04 +0000 (16:34 +0530)
Fixes: http://tracker.ceph.com/issues/36356
Signed-off-by: Kanika Murarka <kmurarka@redhat.com>
17 files changed:
src/pybind/mgr/dashboard/controllers/grafana.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/settings.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/settings.service.ts [changed mode: 0755->0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts
src/pybind/mgr/dashboard/frontend/src/locale/messages.xlf
src/pybind/mgr/dashboard/tests/test_grafana.py

index e24c0cc161b76723117a4042d75b0de2ba799420..ec444c5fca02ebc04ad26df584560e278b540d64 100644 (file)
@@ -1,87 +1,21 @@
 # -*- 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)
@@ -91,25 +25,13 @@ class Grafana(BaseController):
     @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
index 0449b299cc9ca54f176e67064ace36f53a1060d3..a4848790b58da9455f91badbc93d03587a53e5b3 100644 (file)
@@ -43,7 +43,8 @@
   <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>
index ca992c9f16b32e63751a90a28c8d6b5fee1fe14b..219250d2f5a0720891f4748e9fdafaef42a8c288 100644 (file)
@@ -1,7 +1,8 @@
 <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>
index e514b610ed0dbd0791c74f6008df79b749a9ef8d..b0eb93470f8c6b9931bb358fea6a5c5082e3d547 100644 (file)
@@ -27,7 +27,8 @@
   <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>
index a7238b7a3e9b535c4fb1def9c9454726fa3d1c35..ba2c34cfda467f5664c73447b1e1974dbf727135 100644 (file)
@@ -44,7 +44,8 @@
   <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>
index 555d691abca85cac8c153765b0ab2c50a9b6d80b..dadc21beeddba5a4f15fc51a1bae21cf4113cf77 100644 (file)
@@ -57,7 +57,8 @@
   <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>
index 3dea670f77771fd14b0988d66227739826c0bd46..16e48e0464a7904b1f51c15dbdafe82ffe41264a 100644 (file)
@@ -25,8 +25,9 @@
         <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>
@@ -37,7 +38,8 @@
   <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>
index 93a0c17e2c64f3da4d2d26457720b46b24ab3043..d46bbb8c2cceff2b5125c0591db7474867a18e33 100644 (file)
@@ -14,7 +14,8 @@
   <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>
index ee417def63ab7643b963ab4b08cc3ee19122459c..7efc3c11594f838fdbc40a32a88392468a18685e 100644 (file)
@@ -14,7 +14,8 @@
   <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>
index 81b868a5fec22957b207ecdd05acc0890508f7ba..04dfa82a0551b7a920c9e7ae877b8f90345c6870 100644 (file)
@@ -12,4 +12,8 @@ export class LogsService {
   getLogs() {
     return this.http.get('api/logs/all');
   }
+
+  validateDashboardUrl(uid) {
+    return this.http.get(`api/grafana/validation/${uid}`);
+  }
 }
index 10dee3506e1424c223365dfb136510bc66eecc88..faae4237381b406d89af6c23037812de7f1181be 100644 (file)
@@ -29,6 +29,12 @@ describe('SettingsService', () => {
     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);
old mode 100755 (executable)
new mode 100644 (file)
index 9385c05..a722ff6
@@ -27,4 +27,8 @@ export class SettingsService {
   private getSettingsValue(data: any): string {
     return data.value || data.instance || '';
   }
+
+  validateGrafanaDashboardUrl(uid) {
+    return this.http.get(`api/grafana/validation/${uid}`);
+  }
 }
index d5c41961df303407d87ed5765057b764d0196366..477db02a6e55b8649b1f76f61c1202c02df24196 100644 (file)
@@ -7,8 +7,13 @@
   <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
index ea227cb1f14d7e997af49da9a2621424afda6892..568f0cb91073766dbc5d2f3517d6407f9eb39330 100644 (file)
@@ -51,10 +51,17 @@ describe('GrafanaComponent', () => {
       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);
+    });
   });
 });
index 6bbfa47cbdd368e3989fa5780c5fa5ecf272204b..7ed2c76e99093d5e03c8eb813c581bf121e7b42a 100644 (file)
@@ -17,7 +17,6 @@ export class GrafanaComponent implements OnInit, OnChanges {
   url: string;
   protocol: string;
   host: string;
-  dashboardPath: string;
   port: number;
   baseUrl: any;
   panelStyle: any;
@@ -27,11 +26,15 @@ export class GrafanaComponent implements OnInit, OnChanges {
   modeText = 'Change time selection';
   loading = true;
   styles = {};
+  dashboardExist = true;
 
   @Input()
   grafanaPath: string;
   @Input()
   grafanaStyle: string;
+  @Input()
+  uid: string;
+
   docsUrl: string;
 
   constructor(
@@ -73,7 +76,10 @@ export class GrafanaComponent implements OnInit, OnChanges {
   }
 
   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);
   }
 
index 10caf93c6230d63c460efe87a94cda91445fc931..b02ae823a32a0ceeec1baaf76cf1b832342058a3 100644 (file)
           <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&apos;t exist. Please refer to
+  <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>documentation<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> 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="&lt;strong&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ markActionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> 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="&lt;strong&gt;"/>OSD <x id="INTERPOLATION" equiv-text="{{ selection.first().id }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> will be
   <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ actionDescription }}"/><x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> 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>
index 503ad00f60502ade0b39cf937f440ce44e1c0711..da0ce9d9c7c9ae3473de94ae0d4df559bef1ca72 100644 (file)
@@ -1,51 +1,24 @@
-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)