summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Belanger <pabelanger@redhat.com>2015-10-16 13:25:59 -0400
committerPaul Belanger <pabelanger@redhat.com>2015-11-03 12:36:59 -0500
commit2c92819451055138868b5e4213625482df78971d (patch)
tree307abdd0ec75c91f8398ec3329ae51d054623efa
parent57e4e17f20ec62054471f258ce77bb841a24b46f (diff)
Add datasource support0.0.2
We now support the ability to create a datasource using yaml files. Change-Id: I1db38ac25bc309398924c15635ea5dee4eaf264c Signed-off-by: Paul Belanger <pabelanger@redhat.com>
Notes
Notes (review): Verified+2: Jenkins Code-Review+2: yolanda.robla <info@ysoft.biz> Code-Review+2: Timothy R. Chavez <tchavez@bluebox.net> Workflow+1: Timothy R. Chavez <tchavez@bluebox.net> Submitted-by: Jenkins Submitted-at: Tue, 03 Nov 2015 17:47:44 +0000 Reviewed-on: https://review.openstack.org/194265 Project: openstack-infra/grafyaml Branch: refs/heads/master
-rw-r--r--doc/source/api.rst4
-rw-r--r--grafana_dashboards/builder.py52
-rw-r--r--grafana_dashboards/cmd.py10
-rw-r--r--grafana_dashboards/grafana/__init__.py10
-rw-r--r--grafana_dashboards/grafana/dashboard.py13
-rw-r--r--grafana_dashboards/grafana/datasource.py127
-rw-r--r--grafana_dashboards/grafana/utils.py18
-rw-r--r--grafana_dashboards/parser.py38
-rw-r--r--grafana_dashboards/schema/__init__.py32
-rw-r--r--grafana_dashboards/schema/dashboard.py8
-rw-r--r--grafana_dashboards/schema/datasource.py29
-rw-r--r--tests/fixtures/builder/datasource-0001.yaml3
-rw-r--r--tests/fixtures/builder/datasource-0002.yaml3
-rw-r--r--tests/grafana/__init__.py0
-rw-r--r--tests/grafana/test_datasource.py99
-rw-r--r--tests/schema/fixtures/datasource-0001.json11
-rw-r--r--tests/schema/fixtures/datasource-0001.yaml3
-rw-r--r--tests/schema/test_schema.py (renamed from tests/schema/test_dashboard.py)2
-rw-r--r--tests/test_builder.py55
19 files changed, 466 insertions, 51 deletions
diff --git a/doc/source/api.rst b/doc/source/api.rst
index a6b7fad..7d0f52f 100644
--- a/doc/source/api.rst
+++ b/doc/source/api.rst
@@ -7,6 +7,10 @@ API Reference
7 :members: 7 :members:
8 :undoc-members: 8 :undoc-members:
9 9
10.. automodule:: grafana_dashboards.grafana.datasource
11 :members:
12 :undoc-members:
13
10.. automodule:: grafana_dashboards.grafana.dashboard 14.. automodule:: grafana_dashboards.grafana.dashboard
11 :members: 15 :members:
12 :undoc-members: 16 :undoc-members:
diff --git a/grafana_dashboards/builder.py b/grafana_dashboards/builder.py
index 35b9f79..3db6998 100644
--- a/grafana_dashboards/builder.py
+++ b/grafana_dashboards/builder.py
@@ -33,13 +33,14 @@ class Builder(object):
33 key=config.get('grafana', 'apikey')) 33 key=config.get('grafana', 'apikey'))
34 self.parser = YamlParser() 34 self.parser = YamlParser()
35 35
36 def delete_dashboard(self, path): 36 def delete(self, path):
37 self.load_files(path) 37 self.load_files(path)
38 datasources = self.parser.data.get('datasource', {})
39 LOG.info('Number of datasources to be deleted: %d', len(datasources))
40 self._delete_datasource(datasources)
38 dashboards = self.parser.data.get('dashboard', {}) 41 dashboards = self.parser.data.get('dashboard', {})
39 for name in dashboards: 42 LOG.info('Number of dashboards to be deleted: %d', len(dashboards))
40 LOG.debug('Deleting grafana dashboard %s', name) 43 self._delete_dashboard(dashboards)
41 self.grafana.dashboard.delete(name)
42 self.cache.set(name, '')
43 44
44 def load_files(self, path): 45 def load_files(self, path):
45 files_to_process = [] 46 files_to_process = []
@@ -54,14 +55,49 @@ class Builder(object):
54 for fn in files_to_process: 55 for fn in files_to_process:
55 self.parser.parse(fn) 56 self.parser.parse(fn)
56 57
57 def update_dashboard(self, path): 58 def update(self, path):
58 self.load_files(path) 59 self.load_files(path)
60 datasources = self.parser.data.get('datasource', {})
61 LOG.info('Number of datasources to be updated: %d', len(datasources))
62 self._update_datasource(datasources)
59 dashboards = self.parser.data.get('dashboard', {}) 63 dashboards = self.parser.data.get('dashboard', {})
60 LOG.info('Number of dashboards generated: %d', len(dashboards)) 64 LOG.info('Number of dashboards to be updated: %d', len(dashboards))
61 for name in dashboards: 65 self._update_dashboard(dashboards)
66
67 def _delete_dashboard(self, data):
68 for name in data:
69 LOG.debug('Deleting grafana dashboard %s', name)
70 self.grafana.dashboard.delete(name)
71 self.cache.set(name, '')
72
73 def _delete_datasource(self, data):
74 for name in data:
75 LOG.debug('Deleting grafana datasource %s', name)
76 datasource_id = self.grafana.datasource.is_datasource(name)
77 if datasource_id:
78 self.grafana.datasource.delete(datasource_id)
79 self.cache.set(name, '')
80
81 def _update_dashboard(self, data):
82 for name in data:
62 data, md5 = self.parser.get_dashboard(name) 83 data, md5 = self.parser.get_dashboard(name)
63 if self.cache.has_changed(name, md5): 84 if self.cache.has_changed(name, md5):
64 self.grafana.dashboard.create(name, data, overwrite=True) 85 self.grafana.dashboard.create(name, data, overwrite=True)
65 self.cache.set(name, md5) 86 self.cache.set(name, md5)
66 else: 87 else:
67 LOG.debug("'%s' has not changed" % name) 88 LOG.debug("'%s' has not changed" % name)
89
90 def _update_datasource(self, data):
91 for name in data:
92 data, md5 = self.parser.get_datasource(name)
93 if self.cache.has_changed(name, md5):
94 # Check for existing datasource so we can find the
95 # datasource_id.
96 datasource_id = self.grafana.datasource.is_datasource(name)
97 if datasource_id:
98 self.grafana.datasource.update(datasource_id, data)
99 else:
100 self.grafana.datasource.create(name, data)
101 self.cache.set(name, md5)
102 else:
103 LOG.debug("'%s' has not changed" % name)
diff --git a/grafana_dashboards/cmd.py b/grafana_dashboards/cmd.py
index 232bf95..9d5f9c2 100644
--- a/grafana_dashboards/cmd.py
+++ b/grafana_dashboards/cmd.py
@@ -27,9 +27,9 @@ LOG = logging.getLogger(__name__)
27class Client(object): 27class Client(object):
28 28
29 def delete(self): 29 def delete(self):
30 LOG.info('Deleting dashboards in %s', self.args.path) 30 LOG.info('Deleting schema in %s', self.args.path)
31 builder = Builder(self.config) 31 builder = Builder(self.config)
32 builder.delete_dashboard(self.args.path) 32 builder.delete(self.args.path)
33 33
34 def main(self): 34 def main(self):
35 self.parse_arguments() 35 self.parse_arguments()
@@ -90,12 +90,12 @@ class Client(object):
90 logging.basicConfig(level=logging.INFO) 90 logging.basicConfig(level=logging.INFO)
91 91
92 def update(self): 92 def update(self):
93 LOG.info('Updating dashboards in %s', self.args.path) 93 LOG.info('Updating schema in %s', self.args.path)
94 builder = Builder(self.config) 94 builder = Builder(self.config)
95 builder.update_dashboard(self.args.path) 95 builder.update(self.args.path)
96 96
97 def validate(self): 97 def validate(self):
98 LOG.info('Validating dashboards in %s', self.args.path) 98 LOG.info('Validating schema in %s', self.args.path)
99 # NOTE(pabelanger): Disable caching support by default, in an effort 99 # NOTE(pabelanger): Disable caching support by default, in an effort
100 # to improve performance. 100 # to improve performance.
101 self.config.set('cache', 'enabled', 'false') 101 self.config.set('cache', 'enabled', 'false')
diff --git a/grafana_dashboards/grafana/__init__.py b/grafana_dashboards/grafana/__init__.py
index 744a968..afac7e5 100644
--- a/grafana_dashboards/grafana/__init__.py
+++ b/grafana_dashboards/grafana/__init__.py
@@ -12,14 +12,10 @@
12# License for the specific language governing permissions and limitations 12# License for the specific language governing permissions and limitations
13# under the License. 13# under the License.
14 14
15try:
16 from urllib.parse import urljoin
17except ImportError:
18 from urlparse import urljoin
19
20import requests 15import requests
21 16
22from grafana_dashboards.grafana.dashboard import Dashboard 17from grafana_dashboards.grafana.dashboard import Dashboard
18from grafana_dashboards.grafana.datasource import Datasource
23 19
24 20
25class Grafana(object): 21class Grafana(object):
@@ -36,7 +32,6 @@ class Grafana(object):
36 self.server = url 32 self.server = url
37 self.auth = None 33 self.auth = None
38 34
39 base_url = urljoin(self.server, 'api/dashboards/db/')
40 session = requests.Session() 35 session = requests.Session()
41 session.headers.update({ 36 session.headers.update({
42 'Content-Type': 'application/json', 37 'Content-Type': 'application/json',
@@ -47,4 +42,5 @@ class Grafana(object):
47 self.auth = {'Authorization': 'Bearer %s' % key} 42 self.auth = {'Authorization': 'Bearer %s' % key}
48 session.headers.update(self.auth) 43 session.headers.update(self.auth)
49 44
50 self.dashboard = Dashboard(base_url, session) 45 self.dashboard = Dashboard(self.server, session)
46 self.datasource = Datasource(self.server, session)
diff --git a/grafana_dashboards/grafana/dashboard.py b/grafana_dashboards/grafana/dashboard.py
index 68ca57f..3f634b6 100644
--- a/grafana_dashboards/grafana/dashboard.py
+++ b/grafana_dashboards/grafana/dashboard.py
@@ -14,18 +14,15 @@
14 14
15import json 15import json
16 16
17try:
18 from urllib.parse import urljoin
19except ImportError:
20 from urlparse import urljoin
21
22from requests import exceptions 17from requests import exceptions
23 18
19from grafana_dashboards.grafana import utils
20
24 21
25class Dashboard(object): 22class Dashboard(object):
26 23
27 def __init__(self, url, session): 24 def __init__(self, url, session):
28 self.url = url 25 self.url = utils.urljoin(url, 'api/dashboards/db/')
29 self.session = session 26 self.session = session
30 27
31 def create(self, name, data, overwrite=False): 28 def create(self, name, data, overwrite=False):
@@ -64,7 +61,7 @@ class Dashboard(object):
64 :raises Exception: if dashboard failed to delete 61 :raises Exception: if dashboard failed to delete
65 62
66 """ 63 """
67 url = urljoin(self.url, name) 64 url = utils.urljoin(self.url, name)
68 self.session.delete(url) 65 self.session.delete(url)
69 if self.is_dashboard(name): 66 if self.is_dashboard(name):
70 raise Exception('dashboard[%s] failed to delete' % name) 67 raise Exception('dashboard[%s] failed to delete' % name)
@@ -78,7 +75,7 @@ class Dashboard(object):
78 :rtype: dict or None 75 :rtype: dict or None
79 76
80 """ 77 """
81 url = urljoin(self.url, name) 78 url = utils.urljoin(self.url, name)
82 try: 79 try:
83 res = self.session.get(url) 80 res = self.session.get(url)
84 res.raise_for_status() 81 res.raise_for_status()
diff --git a/grafana_dashboards/grafana/datasource.py b/grafana_dashboards/grafana/datasource.py
new file mode 100644
index 0000000..d6edcca
--- /dev/null
+++ b/grafana_dashboards/grafana/datasource.py
@@ -0,0 +1,127 @@
1# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import json
16
17from requests import exceptions
18
19from grafana_dashboards.grafana import utils
20
21
22class Datasource(object):
23
24 def __init__(self, url, session):
25 self.url = utils.urljoin(url, 'api/datasources/')
26 self.session = session
27
28 def create(self, name, data):
29 """Create a new datasource
30
31 :param name: URL friendly title of the datasource
32 :type name: str
33 :param data: Datasource model
34 :type data: dict
35
36 :raises Exception: if datasource already exists
37
38 """
39 if self.is_datasource(name):
40 raise Exception('datasource[%s] already exists' % name)
41
42 res = self.session.post(
43 self.url, data=json.dumps(data))
44
45 res.raise_for_status()
46 return res.json()
47
48 def delete(self, datasource_id):
49 """Delete a datasource
50
51 :param datasource_id: Id number of datasource
52 :type datasource_id: int
53
54 :raises Exception: if datasource failed to delete
55
56 """
57 url = utils.urljoin(self.url, str(datasource_id))
58 self.session.delete(url)
59 if self.get(datasource_id):
60 raise Exception('datasource[%s] failed to delete' % datasource_id)
61
62 def get(self, datasource_id):
63 """Get a datasource
64
65 :param datasource_id: Id number of datasource
66 :type datasource_id: int
67
68 :rtype: dict or None
69
70 """
71 url = utils.urljoin(self.url, str(datasource_id))
72 try:
73 res = self.session.get(url)
74 res.raise_for_status()
75 except exceptions.HTTPError:
76 return None
77
78 return res.json()
79
80 def get_all(self):
81 """List all datasource
82
83 :rtype: dict
84
85 """
86 res = self.session.get(self.url)
87 res.raise_for_status()
88
89 return res.json()
90
91 def is_datasource(self, name):
92 """Check if a datasource exists
93
94 :param name: URL friendly title of the dashboard
95 :type name: str
96
97 :returns: if datasource exists return id number.
98 :rtype: int
99
100 """
101 datasources = self.get_all()
102 for datasource in datasources:
103 if datasource['name'].lower() == name.lower():
104 return datasource['id']
105 return 0
106
107 def update(self, datasource_id, data):
108 """Update an existing datasource
109
110 :param datasource_id: URL friendly title of the dashboard
111 :type datasource_id: int
112 :param data: Datasource model
113 :type data: dict
114 :param overwrite: Overwrite existing dashboard with newer version or
115 with the same dashboard title
116 :type overwrite: bool
117
118 :raises Exception: if datasource already exists
119
120 """
121 url = utils.urljoin(self.url, str(datasource_id))
122
123 res = self.session.put(
124 url, data=json.dumps(data))
125
126 res.raise_for_status()
127 return res.json()
diff --git a/grafana_dashboards/grafana/utils.py b/grafana_dashboards/grafana/utils.py
new file mode 100644
index 0000000..4c715c8
--- /dev/null
+++ b/grafana_dashboards/grafana/utils.py
@@ -0,0 +1,18 @@
1# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15try:
16 from urllib.parse import urljoin # noqa
17except ImportError:
18 from urlparse import urljoin # noqa
diff --git a/grafana_dashboards/parser.py b/grafana_dashboards/parser.py
index 6f17df6..02ac110 100644
--- a/grafana_dashboards/parser.py
+++ b/grafana_dashboards/parser.py
@@ -20,7 +20,9 @@ import yaml
20 20
21from slugify import slugify 21from slugify import slugify
22 22
23from grafana_dashboards.schema.dashboard import Dashboard 23from grafana_dashboards.schema import Schema
24
25LOG = logging.getLogger(__name__)
24 26
25LOG = logging.getLogger(__name__) 27LOG = logging.getLogger(__name__)
26 28
@@ -32,15 +34,18 @@ class YamlParser(object):
32 34
33 def get_dashboard(self, slug): 35 def get_dashboard(self, slug):
34 data = self.data.get('dashboard', {}).get(slug, None) 36 data = self.data.get('dashboard', {}).get(slug, None)
35 md5 = None 37 md5 = self._generate_md5(data)
36 if data:
37 # Sort json keys to help our md5 hash are constant.
38 content = json.dumps(data, sort_keys=True)
39 md5 = hashlib.md5(content.encode('utf-8')).hexdigest()
40 LOG.debug('Dashboard %s: %s' % (slug, md5)) 38 LOG.debug('Dashboard %s: %s' % (slug, md5))
41 39
42 return data, md5 40 return data, md5
43 41
42 def get_datasource(self, slug):
43 data = self.data.get('datasource', {}).get(slug, None)
44 md5 = self._generate_md5(data)
45 LOG.debug('Datasource %s: %s' % (slug, md5))
46
47 return data, md5
48
44 def parse(self, fn): 49 def parse(self, fn):
45 with io.open(fn, 'r', encoding='utf-8') as fp: 50 with io.open(fn, 'r', encoding='utf-8') as fp:
46 self.parse_fp(fp) 51 self.parse_fp(fp)
@@ -51,15 +56,26 @@ class YamlParser(object):
51 for item in result.items(): 56 for item in result.items():
52 group = self.data.get(item[0], {}) 57 group = self.data.get(item[0], {})
53 # Create slug to make it easier to find dashboards. 58 # Create slug to make it easier to find dashboards.
54 title = item[1]['title'] 59 if item[0] == 'dashboard':
55 slug = slugify(title) 60 name = item[1]['title']
61 else:
62 name = item[1]['name']
63 slug = slugify(name)
56 if slug in group: 64 if slug in group:
57 raise Exception( 65 raise Exception(
58 "Duplicate dashboard found in '{0}: '{1}' " 66 "Duplicate {0} found in '{1}: '{2}' "
59 "already defined".format(fp.name, title)) 67 "already defined".format(item[0], fp.name, name))
60 group[slug] = item[1] 68 group[slug] = item[1]
61 self.data[item[0]] = group 69 self.data[item[0]] = group
62 70
63 def validate(self, data): 71 def validate(self, data):
64 schema = Dashboard() 72 schema = Schema()
65 return schema.validate(data) 73 return schema.validate(data)
74
75 def _generate_md5(self, data):
76 md5 = None
77 if data:
78 # Sort json keys to help our md5 hash are constant.
79 content = json.dumps(data, sort_keys=True)
80 md5 = hashlib.md5(content.encode('utf-8')).hexdigest()
81 return md5
diff --git a/grafana_dashboards/schema/__init__.py b/grafana_dashboards/schema/__init__.py
index e69de29..c718cec 100644
--- a/grafana_dashboards/schema/__init__.py
+++ b/grafana_dashboards/schema/__init__.py
@@ -0,0 +1,32 @@
1# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import voluptuous as v
16
17from grafana_dashboards.schema.dashboard import Dashboard
18from grafana_dashboards.schema.datasource import Datasource
19
20
21class Schema(object):
22
23 def validate(self, data):
24 dashboard = Dashboard().get_schema()
25 datasource = Datasource().get_schema()
26
27 schema = v.Schema({
28 v.Optional('dashboard'): dashboard,
29 v.Optional('datasource'): datasource,
30 })
31
32 return schema(data)
diff --git a/grafana_dashboards/schema/dashboard.py b/grafana_dashboards/schema/dashboard.py
index 6572f43..977c248 100644
--- a/grafana_dashboards/schema/dashboard.py
+++ b/grafana_dashboards/schema/dashboard.py
@@ -19,15 +19,11 @@ from grafana_dashboards.schema.row import Row
19 19
20class Dashboard(object): 20class Dashboard(object):
21 21
22 def validate(self, data): 22 def get_schema(self):
23 dashboard = { 23 dashboard = {
24 v.Required('title'): v.All(str, v.Length(min=1)), 24 v.Required('title'): v.All(str, v.Length(min=1)),
25 v.Optional('id'): int, 25 v.Optional('id'): int,
26 } 26 }
27 rows = Row().get_schema() 27 rows = Row().get_schema()
28 dashboard.update(rows.schema) 28 dashboard.update(rows.schema)
29 schema = v.Schema({ 29 return dashboard
30 v.Required('dashboard'): dashboard,
31 })
32
33 return schema(data)
diff --git a/grafana_dashboards/schema/datasource.py b/grafana_dashboards/schema/datasource.py
new file mode 100644
index 0000000..2a27f3b
--- /dev/null
+++ b/grafana_dashboards/schema/datasource.py
@@ -0,0 +1,29 @@
1# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import voluptuous as v
16
17
18class Datasource(object):
19
20 def get_schema(self):
21 datasource = {
22 v.Required('access', default='direct'): v.Any('direct', 'proxy'),
23 v.Required('isDefault', default=False): v.All(bool),
24 v.Required('name'): v.All(str, v.Length(min=1)),
25 v.Required('type', default='graphite'): v.Any('graphite'),
26 v.Required('url'): v.All(str, v.Length(min=1)),
27 v.Optional('orgId'): int,
28 }
29 return datasource
diff --git a/tests/fixtures/builder/datasource-0001.yaml b/tests/fixtures/builder/datasource-0001.yaml
new file mode 100644
index 0000000..2a773de
--- /dev/null
+++ b/tests/fixtures/builder/datasource-0001.yaml
@@ -0,0 +1,3 @@
1datasource:
2 name: Default
3 url: http://graphite.example.org:8080
diff --git a/tests/fixtures/builder/datasource-0002.yaml b/tests/fixtures/builder/datasource-0002.yaml
new file mode 100644
index 0000000..a1f706d
--- /dev/null
+++ b/tests/fixtures/builder/datasource-0002.yaml
@@ -0,0 +1,3 @@
1datasource:
2 name: Default
3 url: http://graphite.example.net:8080
diff --git a/tests/grafana/__init__.py b/tests/grafana/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/grafana/__init__.py
diff --git a/tests/grafana/test_datasource.py b/tests/grafana/test_datasource.py
new file mode 100644
index 0000000..5b2f21c
--- /dev/null
+++ b/tests/grafana/test_datasource.py
@@ -0,0 +1,99 @@
1# Copyright 2015 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import requests_mock
16from testtools import TestCase
17
18from grafana_dashboards.grafana import Grafana
19
20DATASOURCE001 = {
21 "id": 1,
22 "orgId": 1,
23 "name": "foobar",
24 "type": "graphite",
25 "access": "direct",
26 "url": "http://example.org:8080",
27 "password": "",
28 "user": "",
29 "database": "",
30 "basicAuth": False,
31 "basicAuthUser": "",
32 "basicAuthPassword": "",
33 "isDefault": True,
34 "jsonData": None,
35}
36
37DATASOURCE_NOT_FOUND = {
38 "message": "Failed to query datasources"
39}
40
41
42class TestCaseDatasource(TestCase):
43
44 def setUp(self):
45 super(TestCaseDatasource, self).setUp()
46 self.url = 'http://localhost'
47 self.grafana = Grafana(self.url)
48
49 @requests_mock.Mocker()
50 def test_create_new(self, mock_requests):
51 mock_requests.post('/api/datasources/', json=DATASOURCE001)
52 mock_requests.get('/api/datasources/', json=[])
53 res = self.grafana.datasource.create('foobar', DATASOURCE001)
54 self.assertEqual(res, DATASOURCE001)
55
56 @requests_mock.Mocker()
57 def test_get_not_found(self, mock_requests):
58 mock_requests.get(
59 '/api/datasources/1', json=DATASOURCE_NOT_FOUND,
60 status_code=404)
61 res = self.grafana.datasource.get(1)
62 self.assertEqual(res, None)
63
64 @requests_mock.Mocker()
65 def test_get_success(self, mock_requests):
66 mock_requests.get('/api/datasources/1', json=DATASOURCE001)
67 res = self.grafana.datasource.get(1)
68 self.assertEqual(res, DATASOURCE001)
69
70 @requests_mock.Mocker()
71 def test_get_all(self, mock_requests):
72 mock_requests.get(
73 '/api/datasources/', json=[DATASOURCE001])
74 res = self.grafana.datasource.get_all()
75 self.assertEqual(res, [DATASOURCE001])
76
77 @requests_mock.Mocker()
78 def test_get_all_empty(self, mock_requests):
79 mock_requests.get('/api/datasources/', json=[])
80 res = self.grafana.datasource.get_all()
81 self.assertEqual(res, [])
82
83 @requests_mock.Mocker()
84 def test_is_datasource_empty(self, mock_requests):
85 mock_requests.get('/api/datasources/', json=[])
86 res = self.grafana.datasource.is_datasource('foobar')
87 self.assertFalse(res)
88
89 @requests_mock.Mocker()
90 def test_is_datasource_false(self, mock_requests):
91 mock_requests.get('/api/datasources/', json=[DATASOURCE001])
92 res = self.grafana.datasource.is_datasource('new')
93 self.assertFalse(res)
94
95 @requests_mock.Mocker()
96 def test_is_datasource_true(self, mock_requests):
97 mock_requests.get('/api/datasources/', json=[DATASOURCE001])
98 res = self.grafana.datasource.is_datasource('foobar')
99 self.assertTrue(res)
diff --git a/tests/schema/fixtures/datasource-0001.json b/tests/schema/fixtures/datasource-0001.json
new file mode 100644
index 0000000..26f402c
--- /dev/null
+++ b/tests/schema/fixtures/datasource-0001.json
@@ -0,0 +1,11 @@
1{
2 "datasource": {
3 "new-datasource": {
4 "access": "direct",
5 "isDefault": false,
6 "name": "New datasource",
7 "type": "graphite",
8 "url": "http://example.org"
9 }
10 }
11}
diff --git a/tests/schema/fixtures/datasource-0001.yaml b/tests/schema/fixtures/datasource-0001.yaml
new file mode 100644
index 0000000..77c9dfc
--- /dev/null
+++ b/tests/schema/fixtures/datasource-0001.yaml
@@ -0,0 +1,3 @@
1datasource:
2 name: New datasource
3 url: http://example.org
diff --git a/tests/schema/test_dashboard.py b/tests/schema/test_schema.py
index 76c9172..2c24fc0 100644
--- a/tests/schema/test_dashboard.py
+++ b/tests/schema/test_schema.py
@@ -21,6 +21,6 @@ from tests.base import get_scenarios
21from tests.schema.base import TestCase as BaseTestCase 21from tests.schema.base import TestCase as BaseTestCase
22 22
23 23
24class TestCaseSchemaDashboard(TestWithScenarios, TestCase, BaseTestCase): 24class TestCaseSchema(TestWithScenarios, TestCase, BaseTestCase):
25 fixtures_path = os.path.join(os.path.dirname(__file__), 'fixtures') 25 fixtures_path = os.path.join(os.path.dirname(__file__), 'fixtures')
26 scenarios = get_scenarios(fixtures_path) 26 scenarios = get_scenarios(fixtures_path)
diff --git a/tests/test_builder.py b/tests/test_builder.py
index 43366e6..4680e12 100644
--- a/tests/test_builder.py
+++ b/tests/test_builder.py
@@ -36,7 +36,7 @@ class TestCaseBuilder(TestCase):
36 # Create a new builder to avoid duplicate dashboards. 36 # Create a new builder to avoid duplicate dashboards.
37 builder2 = builder.Builder(self.config) 37 builder2 = builder.Builder(self.config)
38 # Delete same dashboard, ensure we delete it from grafana. 38 # Delete same dashboard, ensure we delete it from grafana.
39 builder2.delete_dashboard(path) 39 builder2.delete(path)
40 self.assertEqual(mock_grafana.call_count, 1) 40 self.assertEqual(mock_grafana.call_count, 1)
41 41
42 def test_grafana_defaults(self): 42 def test_grafana_defaults(self):
@@ -54,11 +54,56 @@ class TestCaseBuilder(TestCase):
54 # Create a new builder to avoid duplicate dashboards. 54 # Create a new builder to avoid duplicate dashboards.
55 builder2 = builder.Builder(self.config) 55 builder2 = builder.Builder(self.config)
56 # Update again with same dashboard, ensure we don't update grafana. 56 # Update again with same dashboard, ensure we don't update grafana.
57 builder2.update_dashboard(path) 57 builder2.update(path)
58 self.assertEqual(mock_grafana.call_count, 0) 58 self.assertEqual(mock_grafana.call_count, 0)
59 59
60 @mock.patch('grafana_dashboards.grafana.Datasource.create')
61 def test_create_datasource(self, mock_grafana):
62 path = os.path.join(
63 os.path.dirname(__file__), 'fixtures/builder/datasource-0001.yaml')
64
65 # Create a datasource.
66 self._create_datasource(path)
67 # Create a new builder to avoid duplicate datasources.
68 builder2 = builder.Builder(self.config)
69 # Update again with same datasource, ensure we don't update grafana.
70 builder2.update(path)
71 self.assertEqual(mock_grafana.call_count, 0)
72
73 @mock.patch(
74 'grafana_dashboards.grafana.Datasource.is_datasource',
75 return_value=True)
76 @mock.patch('grafana_dashboards.grafana.Datasource.update')
77 def test_update_datasource(self, mock_is_datasource, mock_update):
78 path = os.path.join(
79 os.path.dirname(__file__), 'fixtures/builder/datasource-0001.yaml')
80
81 # Create a datasource.
82 self._create_datasource(path)
83 # Create a new builder to avoid duplicate datasources.
84 builder2 = builder.Builder(self.config)
85
86 # Same datasource name, different content.
87 path = os.path.join(
88 os.path.dirname(__file__), 'fixtures/builder/datasource-0002.yaml')
89
90 # Update again with same datasource, ensure we update grafana.
91 builder2.update(path)
92 self.assertEqual(mock_is_datasource.call_count, 1)
93 self.assertEqual(mock_update.call_count, 1)
94
60 @mock.patch('grafana_dashboards.grafana.Dashboard.create') 95 @mock.patch('grafana_dashboards.grafana.Dashboard.create')
61 def _update_dashboard(self, path, mock_grafana): 96 def _update_dashboard(self, path, mock_create):
62 self.builder.update_dashboard(path) 97 self.builder.update(path)
63 # Cache is empty, so we should update grafana. 98 # Cache is empty, so we should update grafana.
64 self.assertEqual(mock_grafana.call_count, 1) 99 self.assertEqual(mock_create.call_count, 1)
100
101 @mock.patch(
102 'grafana_dashboards.grafana.Datasource.is_datasource',
103 return_value=False)
104 @mock.patch('grafana_dashboards.grafana.Datasource.create')
105 def _create_datasource(self, path, mock_is_datasource, mock_create):
106 self.builder.update(path)
107 # Cache is empty, so we should update grafana.
108 self.assertEqual(mock_is_datasource.call_count, 1)
109 self.assertEqual(mock_create.call_count, 1)