summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-10-03 19:17:40 +0000
committerGerrit Code Review <review@openstack.org>2015-10-03 19:17:40 +0000
commit82e48fdf0890d6d2e5ccee048a53676d8b53eec6 (patch)
tree4b465fd12d4a2a12a39262d4eeb0eb416f28e972
parent580eb0a1b3d429a8743faa15c8f98fef67062752 (diff)
parent30f7d21f3f33dd1e365a2b888f0cbc7cda9a9c5c (diff)
Merge "Make sure dashboard exists after we create it"0.0.1
-rw-r--r--doc/source/api.rst8
-rwxr-xr-xdoc/source/conf.py6
-rw-r--r--doc/source/index.rst1
-rw-r--r--grafana_dashboards/builder.py12
-rw-r--r--grafana_dashboards/grafana.py89
-rw-r--r--tests/test_grafana.py86
6 files changed, 177 insertions, 25 deletions
diff --git a/doc/source/api.rst b/doc/source/api.rst
new file mode 100644
index 0000000..31eee18
--- /dev/null
+++ b/doc/source/api.rst
@@ -0,0 +1,8 @@
1:title: API reference
2
3API Reference
4=============
5
6.. automodule:: grafana_dashboards.grafana
7 :members:
8 :undoc-members:
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 25fda3e..713eec0 100755
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -22,13 +22,11 @@ sys.path.insert(0, os.path.abspath('../..'))
22# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 22# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
23extensions = [ 23extensions = [
24 'sphinx.ext.autodoc', 24 'sphinx.ext.autodoc',
25 #'sphinx.ext.intersphinx',
26 'oslosphinx' 25 'oslosphinx'
27] 26]
28 27
29# autodoc generation is a bit aggressive and a nuisance when doing heavy 28# Also document __init__
30# text edit cycles. 29autoclass_content = 'both'
31# execute "export SPHINX_DEBUG=1" in your terminal to disable
32 30
33# The suffix of source filenames. 31# The suffix of source filenames.
34source_suffix = '.rst' 32source_suffix = '.rst'
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 0403aee..b62586e 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -13,6 +13,7 @@ Contents
13 usage 13 usage
14 contributing 14 contributing
15 grafana-dashboard 15 grafana-dashboard
16 api
16 17
17Indices and tables 18Indices and tables
18================== 19==================
diff --git a/grafana_dashboards/builder.py b/grafana_dashboards/builder.py
index b56ed68..1d6aabd 100644
--- a/grafana_dashboards/builder.py
+++ b/grafana_dashboards/builder.py
@@ -63,10 +63,10 @@ class Builder(object):
63 def update_dashboard(self, path): 63 def update_dashboard(self, path):
64 self.load_files(path) 64 self.load_files(path)
65 dashboards = self.parser.data.get('dashboard', {}) 65 dashboards = self.parser.data.get('dashboard', {})
66 for item in dashboards: 66 for name in dashboards:
67 data, md5 = self.parser.get_dashboard(item) 67 data, md5 = self.parser.get_dashboard(name)
68 if self.cache.has_changed(item, md5): 68 if self.cache.has_changed(name, md5):
69 self.grafana.create_dashboard(data, overwrite=True) 69 self.grafana.create_dashboard(name, data, overwrite=True)
70 self.cache.set(item, md5) 70 self.cache.set(name, md5)
71 else: 71 else:
72 LOG.debug("'%s' has not changed" % item) 72 LOG.debug("'%s' has not changed" % name)
diff --git a/grafana_dashboards/grafana.py b/grafana_dashboards/grafana.py
index 1ff6619..288c19e 100644
--- a/grafana_dashboards/grafana.py
+++ b/grafana_dashboards/grafana.py
@@ -13,17 +13,33 @@
13# under the License. 13# under the License.
14 14
15import json 15import json
16import requests 16
17try: 17try:
18 from urllib.parse import urljoin 18 from urllib.parse import urljoin
19except ImportError: 19except ImportError:
20 from urlparse import urljoin 20 from urlparse import urljoin
21 21
22import requests
23from requests import exceptions
24
22 25
23class Grafana(object): 26class Grafana(object):
27
24 def __init__(self, url, key=None): 28 def __init__(self, url, key=None):
25 self.url = urljoin(url, 'api/dashboards/db') 29 """Create object for grafana instance
30
31 :param url: URL for Grafana server
32 :type url: str
33 :param key: API token used for authenticate
34 :type key: str
35
36 """
37
38 self.url = urljoin(url, 'api/dashboards/db/')
26 self.session = requests.Session() 39 self.session = requests.Session()
40 self.session.headers.update({
41 'Content-Type': 'application/json',
42 })
27 # NOTE(pabelanger): Grafana 2.1.0 added basic auth support so now the 43 # NOTE(pabelanger): Grafana 2.1.0 added basic auth support so now the
28 # api key is optional. 44 # api key is optional.
29 if key: 45 if key:
@@ -31,14 +47,73 @@ class Grafana(object):
31 'Authorization': 'Bearer %s' % key, 47 'Authorization': 'Bearer %s' % key,
32 }) 48 })
33 49
34 def create_dashboard(self, data, overwrite=False): 50 def assert_dashboard_exists(self, name):
51 """Raise an exception if dashboard does not exist
52
53 :param name: URL friendly title of the dashboard
54 :type name: str
55 :raises Exception: if dashboard does not exist
56
57 """
58 if not self.is_dashboard(name):
59 raise Exception('dashboard[%s] does not exist' % name)
60
61 def create_dashboard(self, name, data, overwrite=False):
62 """Create a new dashboard
63
64 :param name: URL friendly title of the dashboard
65 :type name: str
66 :param data: Dashboard model
67 :type data: dict
68 :param overwrite: Overwrite existing dashboard with newer version or
69 with the same dashboard title
70 :type overwrite: bool
71
72 :raises Exception: if dashboard already exists
73
74 """
35 dashboard = { 75 dashboard = {
36 'dashboard': data, 76 'dashboard': data,
37 'overwrite': overwrite, 77 'overwrite': overwrite,
38 } 78 }
39 headers = { 79 if not overwrite and self.is_dashboard(name):
40 'Content-Type': 'application/json', 80 raise Exception('dashboard[%s] already exists' % name)
41 } 81
42 res = self.session.post( 82 res = self.session.post(
43 self.url, data=json.dumps(dashboard), headers=headers) 83 self.url, data=json.dumps(dashboard))
84
44 res.raise_for_status() 85 res.raise_for_status()
86 self.assert_dashboard_exists(name)
87
88 def get_dashboard(self, name):
89 """Get a dashboard
90
91 :param name: URL friendly title of the dashboard
92 :type name: str
93
94 :rtype: dict or None
95
96 """
97 url = urljoin(self.url, name)
98 try:
99 res = self.session.get(url)
100 res.raise_for_status()
101 except exceptions.HTTPError:
102 return None
103
104 return res.json()
105
106 def is_dashboard(self, name):
107 """Check if a dashboard exists
108
109 :param name: URL friendly title of the dashboard
110 :type name: str
111
112 :returns: True if dashboard exists
113 :rtype: bool
114
115 """
116 res = self.get_dashboard(name)
117 if res and res['meta']['slug'] == name:
118 return True
119 return False
diff --git a/tests/test_grafana.py b/tests/test_grafana.py
index beb0f7c..f05f0b4 100644
--- a/tests/test_grafana.py
+++ b/tests/test_grafana.py
@@ -17,12 +17,35 @@ from testtools import TestCase
17 17
18from grafana_dashboards.grafana import Grafana 18from grafana_dashboards.grafana import Grafana
19 19
20CREATE_NEW_DASHBOARD = {
21 "meta": {
22 "canSave": True,
23 "created": "0001-01-01T00:00:00Z",
24 "canStar": True,
25 "expires": "0001-01-01T00:00:00Z",
26 "slug": "new-dashboard",
27 "type": "db",
28 "canEdit": True
29 },
30 "dashboard": {
31 "rows": [],
32 "id": 1,
33 "version": 0,
34 "title": "New dashboard"
35 }
36}
37
38DASHBOARD_NOT_FOUND = {
39 "message": "Dashboard not found"
40}
41
20 42
21class TestCaseGrafana(TestCase): 43class TestCaseGrafana(TestCase):
22 44
23 def setUp(self): 45 def setUp(self):
24 super(TestCaseGrafana, self).setUp() 46 super(TestCaseGrafana, self).setUp()
25 self.url = 'http://localhost' 47 self.url = 'http://localhost'
48 self.grafana = Grafana(self.url)
26 49
27 def test_init(self): 50 def test_init(self):
28 grafana = Grafana(self.url) 51 grafana = Grafana(self.url)
@@ -36,16 +59,63 @@ class TestCaseGrafana(TestCase):
36 self.assertEqual(headers['Authorization'], 'Bearer %s' % apikey) 59 self.assertEqual(headers['Authorization'], 'Bearer %s' % apikey)
37 60
38 @requests_mock.Mocker() 61 @requests_mock.Mocker()
39 def test_create_dashboard_apikey(self, mock_requests): 62 def test_assert_dashboard_exists_failure(self, mock_requests):
40 grafana = Grafana(self.url) 63 mock_requests.get(
41 mock_requests.register_uri('POST', '/api/dashboards/db') 64 '/api/dashboards/db/new-dashboard', json=DASHBOARD_NOT_FOUND,
65 status_code=404)
66 self.assertRaises(
67 Exception, self.grafana.assert_dashboard_exists, 'new-dashboard')
68
69 @requests_mock.Mocker()
70 def test_create_dashboard_new(self, mock_requests):
71 def post_callback(request, context):
72 mock_requests.get(
73 '/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
74 return True
75
76 mock_requests.post('/api/dashboards/db/', json=post_callback)
77 mock_requests.get(
78 '/api/dashboards/db/new-dashboard', json=DASHBOARD_NOT_FOUND,
79 status_code=404)
80
81 data = {
82 "dashboard": {
83 "title": "New dashboard",
84 },
85 "slug": 'new-dashboard',
86 }
87 self.grafana.create_dashboard(
88 name=data['slug'], data=data['dashboard'])
89 self.assertEqual(mock_requests.call_count, 3)
90
91 @requests_mock.Mocker()
92 def test_create_dashboard_overwrite(self, mock_requests):
93 mock_requests.post('/api/dashboards/db/')
94 mock_requests.get(
95 '/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
96 data = {
97 "dashboard": {
98 "title": "New dashboard",
99 },
100 "slug": 'new-dashboard',
101 }
102 self.grafana.create_dashboard(
103 name=data['slug'], data=data['dashboard'], overwrite=True)
104 self.assertEqual(mock_requests.call_count, 2)
105
106 @requests_mock.Mocker()
107 def test_create_dashboard_existing(self, mock_requests):
108 mock_requests.post('/api/dashboards/db/')
109 mock_requests.get(
110 '/api/dashboards/db/new-dashboard', json=CREATE_NEW_DASHBOARD)
42 data = { 111 data = {
43 "dashboard": { 112 "dashboard": {
44 "title": "New dashboard", 113 "title": "New dashboard",
45 } 114 },
115 "slug": 'new-dashboard',
46 } 116 }
47 grafana.create_dashboard(data) 117 self.assertRaises(
118 Exception, self.grafana.create_dashboard, name=data['slug'],
119 data=data['dashboard'], overwrite=False)
120
48 self.assertEqual(mock_requests.call_count, 1) 121 self.assertEqual(mock_requests.call_count, 1)
49 headers = mock_requests.last_request.headers
50 self.assertIn('Content-Type', headers)
51 self.assertEqual(headers['Content-Type'], 'application/json')