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:
Paul Belanger 2015-10-02 11:35:16 -04:00
parent 5e88ddde1e
commit 30f7d21f3f
6 changed files with 177 additions and 25 deletions

8
doc/source/api.rst Normal file
View File

@ -0,0 +1,8 @@
:title: API reference
API Reference
=============
.. automodule:: grafana_dashboards.grafana
:members:
:undoc-members:

View File

@ -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'

View File

@ -13,6 +13,7 @@ Contents
usage
contributing
grafana-dashboard
api
Indices and tables
==================

View File

@ -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)

View File

@ -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

View File

@ -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')