Make sure dashboard exists after we create it
The long term goal here is to eventually create python-grafana from our embedded grafana.py file. So, most of this code just makes the Grafana object more user friendly. However, we've also added some validation around create_dashboard, we added some checks before we create and after. To ensure we actually created our new dashboards. Finally, add some sphinx docs since everybody loves documentation. Change-Id: Icbba403afe5208fbef1855118d4c3f4293461e00 Signed-off-by: Paul Belanger <pabelanger@redhat.com>
This commit is contained in:
parent
5e88ddde1e
commit
30f7d21f3f
8
doc/source/api.rst
Normal file
8
doc/source/api.rst
Normal file
@ -0,0 +1,8 @@
|
||||
:title: API reference
|
||||
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. automodule:: grafana_dashboards.grafana
|
||||
:members:
|
||||
:undoc-members:
|
@ -22,13 +22,11 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
# Also document __init__
|
||||
autoclass_content = 'both'
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
@ -13,6 +13,7 @@ Contents
|
||||
usage
|
||||
contributing
|
||||
grafana-dashboard
|
||||
api
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@ -48,10 +48,10 @@ class Builder(object):
|
||||
def update_dashboard(self, path):
|
||||
self.parser.parse(path)
|
||||
dashboards = self.parser.data.get('dashboard', {})
|
||||
for item in dashboards:
|
||||
data, md5 = self.parser.get_dashboard(item)
|
||||
if self.cache.has_changed(item, md5):
|
||||
self.grafana.create_dashboard(data, overwrite=True)
|
||||
self.cache.set(item, md5)
|
||||
for name in dashboards:
|
||||
data, md5 = self.parser.get_dashboard(name)
|
||||
if self.cache.has_changed(name, md5):
|
||||
self.grafana.create_dashboard(name, data, overwrite=True)
|
||||
self.cache.set(name, md5)
|
||||
else:
|
||||
LOG.debug("'%s' has not changed" % item)
|
||||
LOG.debug("'%s' has not changed" % name)
|
||||
|
@ -13,17 +13,33 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
try:
|
||||
from urllib.parse import urljoin
|
||||
except ImportError:
|
||||
from urlparse import urljoin
|
||||
|
||||
import requests
|
||||
from requests import exceptions
|
||||
|
||||
|
||||
class Grafana(object):
|
||||
|
||||
def __init__(self, url, key=None):
|
||||
self.url = urljoin(url, 'api/dashboards/db')
|
||||
"""Create object for grafana instance
|
||||
|
||||
:param url: URL for Grafana server
|
||||
:type url: str
|
||||
:param key: API token used for authenticate
|
||||
:type key: str
|
||||
|
||||
"""
|
||||
|
||||
self.url = urljoin(url, 'api/dashboards/db/')
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
# NOTE(pabelanger): Grafana 2.1.0 added basic auth support so now the
|
||||
# api key is optional.
|
||||
if key:
|
||||
@ -31,14 +47,73 @@ class Grafana(object):
|
||||
'Authorization': 'Bearer %s' % key,
|
||||
})
|
||||
|
||||
def create_dashboard(self, data, overwrite=False):
|
||||
def assert_dashboard_exists(self, name):
|
||||
"""Raise an exception if dashboard does not exist
|
||||
|
||||
:param name: URL friendly title of the dashboard
|
||||
:type name: str
|
||||
:raises Exception: if dashboard does not exist
|
||||
|
||||
"""
|
||||
if not self.is_dashboard(name):
|
||||
raise Exception('dashboard[%s] does not exist' % name)
|
||||
|
||||
def create_dashboard(self, name, data, overwrite=False):
|
||||
"""Create a new dashboard
|
||||
|
||||
:param name: URL friendly title of the dashboard
|
||||
:type name: str
|
||||
:param data: Dashboard model
|
||||
:type data: dict
|
||||
:param overwrite: Overwrite existing dashboard with newer version or
|
||||
with the same dashboard title
|
||||
:type overwrite: bool
|
||||
|
||||
:raises Exception: if dashboard already exists
|
||||
|
||||
"""
|
||||
dashboard = {
|
||||
'dashboard': data,
|
||||
'overwrite': overwrite,
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if not overwrite and self.is_dashboard(name):
|
||||
raise Exception('dashboard[%s] already exists' % name)
|
||||
|
||||
res = self.session.post(
|
||||
self.url, data=json.dumps(dashboard), headers=headers)
|
||||
self.url, data=json.dumps(dashboard))
|
||||
|
||||
res.raise_for_status()
|
||||
self.assert_dashboard_exists(name)
|
||||
|
||||
def get_dashboard(self, name):
|
||||
"""Get a dashboard
|
||||
|
||||
:param name: URL friendly title of the dashboard
|
||||
:type name: str
|
||||
|
||||
:rtype: dict or None
|
||||
|
||||
"""
|
||||
url = urljoin(self.url, name)
|
||||
try:
|
||||
res = self.session.get(url)
|
||||
res.raise_for_status()
|
||||
except exceptions.HTTPError:
|
||||
return None
|
||||
|
||||
return res.json()
|
||||
|
||||
def is_dashboard(self, name):
|
||||
"""Check if a dashboard exists
|
||||
|
||||
:param name: URL friendly title of the dashboard
|
||||
:type name: str
|
||||
|
||||
:returns: True if dashboard exists
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
res = self.get_dashboard(name)
|
||||
if res and res['meta']['slug'] == name:
|
||||
return True
|
||||
return False
|
||||
|
@ -17,12 +17,35 @@ from testtools import TestCase
|
||||
|
||||
from grafana_dashboards.grafana import Grafana
|
||||
|
||||
CREATE_NEW_DASHBOARD = {
|
||||
"meta": {
|
||||
"canSave": True,
|
||||
"created": "0001-01-01T00:00:00Z",
|
||||
"canStar": True,
|
||||
"expires": "0001-01-01T00:00:00Z",
|
||||
"slug": "new-dashboard",
|
||||
"type": "db",
|
||||
"canEdit": True
|
||||
},
|
||||
"dashboard": {
|
||||
"rows": [],
|
||||
"id": 1,
|
||||
"version": 0,
|
||||
"title": "New dashboard"
|
||||
}
|
||||
}
|
||||
|
||||
DASHBOARD_NOT_FOUND = {
|
||||
"message": "Dashboard not found"
|
||||
}
|
||||
|
||||
|
||||
class TestCaseGrafana(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCaseGrafana, self).setUp()
|
||||
self.url = 'http://localhost'
|
||||
self.grafana = Grafana(self.url)
|
||||
|
||||
def test_init(self):
|
||||
grafana = Grafana(self.url)
|
||||
@ -36,16 +59,63 @@ class TestCaseGrafana(TestCase):
|
||||
self.assertEqual(headers['Authorization'], 'Bearer %s' % apikey)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create_dashboard_apikey(self, mock_requests):
|
||||
grafana = Grafana(self.url)
|
||||
mock_requests.register_uri('POST', '/api/dashboards/db')
|
||||
def test_assert_dashboard_exists_failure(self, mock_requests):
|
||||
mock_requests.get(
|
||||
'/api/dashboards/db/new-dashboard', json=DASHBOARD_NOT_FOUND,
|
||||
status_code=404)
|
||||
self.assertRaises(
|
||||
Exception, self.grafana.assert_dashboard_exists, 'new-dashboard')
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create_dashboard_new(self, mock_requests):
|
||||
def post_callback(request, context):
|
||||
mock_requests.get(
|
||||
'/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
|
||||
return True
|
||||
|
||||
mock_requests.post('/api/dashboards/db/', json=post_callback)
|
||||
mock_requests.get(
|
||||
'/api/dashboards/db/new-dashboard', json=DASHBOARD_NOT_FOUND,
|
||||
status_code=404)
|
||||
|
||||
data = {
|
||||
"dashboard": {
|
||||
"title": "New dashboard",
|
||||
}
|
||||
},
|
||||
"slug": 'new-dashboard',
|
||||
}
|
||||
grafana.create_dashboard(data)
|
||||
self.grafana.create_dashboard(
|
||||
name=data['slug'], data=data['dashboard'])
|
||||
self.assertEqual(mock_requests.call_count, 3)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create_dashboard_overwrite(self, mock_requests):
|
||||
mock_requests.post('/api/dashboards/db/')
|
||||
mock_requests.get(
|
||||
'/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
|
||||
data = {
|
||||
"dashboard": {
|
||||
"title": "New dashboard",
|
||||
},
|
||||
"slug": 'new-dashboard',
|
||||
}
|
||||
self.grafana.create_dashboard(
|
||||
name=data['slug'], data=data['dashboard'], overwrite=True)
|
||||
self.assertEqual(mock_requests.call_count, 2)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create_dashboard_existing(self, mock_requests):
|
||||
mock_requests.post('/api/dashboards/db/')
|
||||
mock_requests.get(
|
||||
'/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
|
||||
data = {
|
||||
"dashboard": {
|
||||
"title": "New dashboard",
|
||||
},
|
||||
"slug": 'new-dashboard',
|
||||
}
|
||||
self.assertRaises(
|
||||
Exception, self.grafana.create_dashboard, name=data['slug'],
|
||||
data=data['dashboard'], overwrite=False)
|
||||
|
||||
self.assertEqual(mock_requests.call_count, 1)
|
||||
headers = mock_requests.last_request.headers
|
||||
self.assertIn('Content-Type', headers)
|
||||
self.assertEqual(headers['Content-Type'], 'application/json')
|
||||
|
Loading…
Reference in New Issue
Block a user