Browse Source

Add datasource support

We now support the ability to create a datasource using yaml files.

Change-Id: I1db38ac25bc309398924c15635ea5dee4eaf264c
Signed-off-by: Paul Belanger <pabelanger@redhat.com>
tags/0.0.2^0
Paul Belanger 3 years ago
parent
commit
2c92819451

+ 4
- 0
doc/source/api.rst View File

@@ -7,6 +7,10 @@ API Reference
7 7
     :members:
8 8
     :undoc-members:
9 9
 
10
+.. automodule:: grafana_dashboards.grafana.datasource
11
+    :members:
12
+    :undoc-members:
13
+
10 14
 .. automodule:: grafana_dashboards.grafana.dashboard
11 15
     :members:
12 16
     :undoc-members:

+ 44
- 8
grafana_dashboards/builder.py View File

@@ -33,13 +33,14 @@ class Builder(object):
33 33
             key=config.get('grafana', 'apikey'))
34 34
         self.parser = YamlParser()
35 35
 
36
-    def delete_dashboard(self, path):
36
+    def delete(self, path):
37 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 41
         dashboards = self.parser.data.get('dashboard', {})
39
-        for name in dashboards:
40
-            LOG.debug('Deleting grafana dashboard %s', name)
41
-            self.grafana.dashboard.delete(name)
42
-            self.cache.set(name, '')
42
+        LOG.info('Number of dashboards to be deleted: %d', len(dashboards))
43
+        self._delete_dashboard(dashboards)
43 44
 
44 45
     def load_files(self, path):
45 46
         files_to_process = []
@@ -54,14 +55,49 @@ class Builder(object):
54 55
         for fn in files_to_process:
55 56
             self.parser.parse(fn)
56 57
 
57
-    def update_dashboard(self, path):
58
+    def update(self, path):
58 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 63
         dashboards = self.parser.data.get('dashboard', {})
60
-        LOG.info('Number of dashboards generated: %d', len(dashboards))
61
-        for name in dashboards:
64
+        LOG.info('Number of dashboards to be updated: %d', len(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 83
             data, md5 = self.parser.get_dashboard(name)
63 84
             if self.cache.has_changed(name, md5):
64 85
                 self.grafana.dashboard.create(name, data, overwrite=True)
65 86
                 self.cache.set(name, md5)
66 87
             else:
67 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)

+ 5
- 5
grafana_dashboards/cmd.py View File

@@ -27,9 +27,9 @@ LOG = logging.getLogger(__name__)
27 27
 class Client(object):
28 28
 
29 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 31
         builder = Builder(self.config)
32
-        builder.delete_dashboard(self.args.path)
32
+        builder.delete(self.args.path)
33 33
 
34 34
     def main(self):
35 35
         self.parse_arguments()
@@ -90,12 +90,12 @@ class Client(object):
90 90
             logging.basicConfig(level=logging.INFO)
91 91
 
92 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 94
         builder = Builder(self.config)
95
-        builder.update_dashboard(self.args.path)
95
+        builder.update(self.args.path)
96 96
 
97 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 99
         # NOTE(pabelanger): Disable caching support by default, in an effort
100 100
         # to improve performance.
101 101
         self.config.set('cache', 'enabled', 'false')

+ 3
- 7
grafana_dashboards/grafana/__init__.py View File

@@ -12,14 +12,10 @@
12 12
 # License for the specific language governing permissions and limitations
13 13
 # under the License.
14 14
 
15
-try:
16
-    from urllib.parse import urljoin
17
-except ImportError:
18
-    from urlparse import urljoin
19
-
20 15
 import requests
21 16
 
22 17
 from grafana_dashboards.grafana.dashboard import Dashboard
18
+from grafana_dashboards.grafana.datasource import Datasource
23 19
 
24 20
 
25 21
 class Grafana(object):
@@ -36,7 +32,6 @@ class Grafana(object):
36 32
         self.server = url
37 33
         self.auth = None
38 34
 
39
-        base_url = urljoin(self.server, 'api/dashboards/db/')
40 35
         session = requests.Session()
41 36
         session.headers.update({
42 37
             'Content-Type': 'application/json',
@@ -47,4 +42,5 @@ class Grafana(object):
47 42
             self.auth = {'Authorization': 'Bearer %s' % key}
48 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)

+ 5
- 8
grafana_dashboards/grafana/dashboard.py View File

@@ -14,18 +14,15 @@
14 14
 
15 15
 import json
16 16
 
17
-try:
18
-    from urllib.parse import urljoin
19
-except ImportError:
20
-    from urlparse import urljoin
21
-
22 17
 from requests import exceptions
23 18
 
19
+from grafana_dashboards.grafana import utils
20
+
24 21
 
25 22
 class Dashboard(object):
26 23
 
27 24
     def __init__(self, url, session):
28
-        self.url = url
25
+        self.url = utils.urljoin(url, 'api/dashboards/db/')
29 26
         self.session = session
30 27
 
31 28
     def create(self, name, data, overwrite=False):
@@ -64,7 +61,7 @@ class Dashboard(object):
64 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 65
         self.session.delete(url)
69 66
         if self.is_dashboard(name):
70 67
             raise Exception('dashboard[%s] failed to delete' % name)
@@ -78,7 +75,7 @@ class Dashboard(object):
78 75
         :rtype: dict or None
79 76
 
80 77
         """
81
-        url = urljoin(self.url, name)
78
+        url = utils.urljoin(self.url, name)
82 79
         try:
83 80
             res = self.session.get(url)
84 81
             res.raise_for_status()

+ 127
- 0
grafana_dashboards/grafana/datasource.py View File

@@ -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
+
15
+import json
16
+
17
+from requests import exceptions
18
+
19
+from grafana_dashboards.grafana import utils
20
+
21
+
22
+class 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()

+ 18
- 0
grafana_dashboards/grafana/utils.py View File

@@ -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
+
15
+try:
16
+    from urllib.parse import urljoin  # noqa
17
+except ImportError:
18
+    from urlparse import urljoin  # noqa

+ 27
- 11
grafana_dashboards/parser.py View File

@@ -20,7 +20,9 @@ import yaml
20 20
 
21 21
 from slugify import slugify
22 22
 
23
-from grafana_dashboards.schema.dashboard import Dashboard
23
+from grafana_dashboards.schema import Schema
24
+
25
+LOG = logging.getLogger(__name__)
24 26
 
25 27
 LOG = logging.getLogger(__name__)
26 28
 
@@ -32,15 +34,18 @@ class YamlParser(object):
32 34
 
33 35
     def get_dashboard(self, slug):
34 36
         data = self.data.get('dashboard', {}).get(slug, None)
35
-        md5 = None
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()
37
+        md5 = self._generate_md5(data)
40 38
         LOG.debug('Dashboard %s: %s' % (slug, md5))
41 39
 
42 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 49
     def parse(self, fn):
45 50
         with io.open(fn, 'r', encoding='utf-8') as fp:
46 51
             self.parse_fp(fp)
@@ -51,15 +56,26 @@ class YamlParser(object):
51 56
         for item in result.items():
52 57
             group = self.data.get(item[0], {})
53 58
             # Create slug to make it easier to find dashboards.
54
-            title = item[1]['title']
55
-            slug = slugify(title)
59
+            if item[0] == 'dashboard':
60
+                name = item[1]['title']
61
+            else:
62
+                name = item[1]['name']
63
+            slug = slugify(name)
56 64
             if slug in group:
57 65
                 raise Exception(
58
-                    "Duplicate dashboard found in '{0}: '{1}' "
59
-                    "already defined".format(fp.name, title))
66
+                    "Duplicate {0} found in '{1}: '{2}' "
67
+                    "already defined".format(item[0], fp.name, name))
60 68
             group[slug] = item[1]
61 69
             self.data[item[0]] = group
62 70
 
63 71
     def validate(self, data):
64
-        schema = Dashboard()
72
+        schema = Schema()
65 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

+ 32
- 0
grafana_dashboards/schema/__init__.py View File

@@ -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
+
15
+import voluptuous as v
16
+
17
+from grafana_dashboards.schema.dashboard import Dashboard
18
+from grafana_dashboards.schema.datasource import Datasource
19
+
20
+
21
+class 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)

+ 2
- 6
grafana_dashboards/schema/dashboard.py View File

@@ -19,15 +19,11 @@ from grafana_dashboards.schema.row import Row
19 19
 
20 20
 class Dashboard(object):
21 21
 
22
-    def validate(self, data):
22
+    def get_schema(self):
23 23
         dashboard = {
24 24
             v.Required('title'): v.All(str, v.Length(min=1)),
25 25
             v.Optional('id'): int,
26 26
         }
27 27
         rows = Row().get_schema()
28 28
         dashboard.update(rows.schema)
29
-        schema = v.Schema({
30
-            v.Required('dashboard'): dashboard,
31
-        })
32
-
33
-        return schema(data)
29
+        return dashboard

+ 29
- 0
grafana_dashboards/schema/datasource.py View File

@@ -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
+
15
+import voluptuous as v
16
+
17
+
18
+class 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

+ 3
- 0
tests/fixtures/builder/datasource-0001.yaml View File

@@ -0,0 +1,3 @@
1
+datasource:
2
+  name: Default
3
+  url: http://graphite.example.org:8080

+ 3
- 0
tests/fixtures/builder/datasource-0002.yaml View File

@@ -0,0 +1,3 @@
1
+datasource:
2
+  name: Default
3
+  url: http://graphite.example.net:8080

+ 0
- 0
tests/grafana/__init__.py View File


+ 99
- 0
tests/grafana/test_datasource.py View File

@@ -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
+
15
+import requests_mock
16
+from testtools import TestCase
17
+
18
+from grafana_dashboards.grafana import Grafana
19
+
20
+DATASOURCE001 = {
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
+
37
+DATASOURCE_NOT_FOUND = {
38
+    "message": "Failed to query datasources"
39
+}
40
+
41
+
42
+class 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)

+ 11
- 0
tests/schema/fixtures/datasource-0001.json View File

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

+ 3
- 0
tests/schema/fixtures/datasource-0001.yaml View File

@@ -0,0 +1,3 @@
1
+datasource:
2
+  name: New datasource
3
+  url: http://example.org

tests/schema/test_dashboard.py → tests/schema/test_schema.py View File

@@ -21,6 +21,6 @@ from tests.base import get_scenarios
21 21
 from tests.schema.base import TestCase as BaseTestCase
22 22
 
23 23
 
24
-class TestCaseSchemaDashboard(TestWithScenarios, TestCase, BaseTestCase):
24
+class TestCaseSchema(TestWithScenarios, TestCase, BaseTestCase):
25 25
     fixtures_path = os.path.join(os.path.dirname(__file__), 'fixtures')
26 26
     scenarios = get_scenarios(fixtures_path)

+ 50
- 5
tests/test_builder.py View File

@@ -36,7 +36,7 @@ class TestCaseBuilder(TestCase):
36 36
         # Create a new builder to avoid duplicate dashboards.
37 37
         builder2 = builder.Builder(self.config)
38 38
         # Delete same dashboard, ensure we delete it from grafana.
39
-        builder2.delete_dashboard(path)
39
+        builder2.delete(path)
40 40
         self.assertEqual(mock_grafana.call_count, 1)
41 41
 
42 42
     def test_grafana_defaults(self):
@@ -54,11 +54,56 @@ class TestCaseBuilder(TestCase):
54 54
         # Create a new builder to avoid duplicate dashboards.
55 55
         builder2 = builder.Builder(self.config)
56 56
         # Update again with same dashboard, ensure we don't update grafana.
57
-        builder2.update_dashboard(path)
57
+        builder2.update(path)
58 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 95
     @mock.patch('grafana_dashboards.grafana.Dashboard.create')
61
-    def _update_dashboard(self, path, mock_grafana):
62
-        self.builder.update_dashboard(path)
96
+    def _update_dashboard(self, path, mock_create):
97
+        self.builder.update(path)
63 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)

Loading…
Cancel
Save