Merge "Make sure dashboard exists after we create it"

This commit is contained in:
Jenkins 2015-10-03 19:17:40 +00:00 committed by Gerrit Code Review
commit 82e48fdf08
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 coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'oslosphinx' 'oslosphinx'
] ]
# autodoc generation is a bit aggressive and a nuisance when doing heavy # Also document __init__
# text edit cycles. autoclass_content = 'both'
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = '.rst'

View File

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

View File

@ -63,10 +63,10 @@ class Builder(object):
def update_dashboard(self, path): def update_dashboard(self, path):
self.load_files(path) self.load_files(path)
dashboards = self.parser.data.get('dashboard', {}) dashboards = self.parser.data.get('dashboard', {})
for item in dashboards: for name in dashboards:
data, md5 = self.parser.get_dashboard(item) data, md5 = self.parser.get_dashboard(name)
if self.cache.has_changed(item, md5): if self.cache.has_changed(name, md5):
self.grafana.create_dashboard(data, overwrite=True) self.grafana.create_dashboard(name, data, overwrite=True)
self.cache.set(item, md5) self.cache.set(name, md5)
else: else:
LOG.debug("'%s' has not changed" % item) LOG.debug("'%s' has not changed" % name)

View File

@ -13,17 +13,33 @@
# under the License. # under the License.
import json import json
import requests
try: try:
from urllib.parse import urljoin from urllib.parse import urljoin
except ImportError: except ImportError:
from urlparse import urljoin from urlparse import urljoin
import requests
from requests import exceptions
class Grafana(object): class Grafana(object):
def __init__(self, url, key=None): 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 = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json',
})
# NOTE(pabelanger): Grafana 2.1.0 added basic auth support so now the # NOTE(pabelanger): Grafana 2.1.0 added basic auth support so now the
# api key is optional. # api key is optional.
if key: if key:
@ -31,14 +47,73 @@ class Grafana(object):
'Authorization': 'Bearer %s' % key, '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 = {
'dashboard': data, 'dashboard': data,
'overwrite': overwrite, 'overwrite': overwrite,
} }
headers = { if not overwrite and self.is_dashboard(name):
'Content-Type': 'application/json', raise Exception('dashboard[%s] already exists' % name)
}
res = self.session.post( res = self.session.post(
self.url, data=json.dumps(dashboard), headers=headers) self.url, data=json.dumps(dashboard))
res.raise_for_status() 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 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): class TestCaseGrafana(TestCase):
def setUp(self): def setUp(self):
super(TestCaseGrafana, self).setUp() super(TestCaseGrafana, self).setUp()
self.url = 'http://localhost' self.url = 'http://localhost'
self.grafana = Grafana(self.url)
def test_init(self): def test_init(self):
grafana = Grafana(self.url) grafana = Grafana(self.url)
@ -36,16 +59,63 @@ class TestCaseGrafana(TestCase):
self.assertEqual(headers['Authorization'], 'Bearer %s' % apikey) self.assertEqual(headers['Authorization'], 'Bearer %s' % apikey)
@requests_mock.Mocker() @requests_mock.Mocker()
def test_create_dashboard_apikey(self, mock_requests): def test_assert_dashboard_exists_failure(self, mock_requests):
grafana = Grafana(self.url) mock_requests.get(
mock_requests.register_uri('POST', '/api/dashboards/db') '/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 = { data = {
"dashboard": { "dashboard": {
"title": "New 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) 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')