Merge ~aluria/charm-grafana:bug/1822329-tests into ~prometheus-charmers/charm-grafana:rewrite

Proposed by Alvaro Uria
Status: Rejected
Rejected by: Haw Loeung
Proposed branch: ~aluria/charm-grafana:bug/1822329-tests
Merge into: ~prometheus-charmers/charm-grafana:rewrite
Diff against target: 1080 lines (+662/-159)
16 files modified
.gitignore (+4/-0)
actions/grafana_utils.py (+1/-1)
config.yaml (+28/-62)
dev/null (+0/-43)
lib/lib_grafana/db.py (+1/-1)
lib/lib_grafana/helpers.py (+4/-0)
reactive/grafana.py (+8/-10)
tests/functional/bundle.yaml (+189/-0)
tests/functional/conftest.py (+1/-2)
tests/functional/overlay.yaml.j2 (+11/-0)
tests/functional/requirements.txt (+1/-0)
tests/functional/test_deploy.py (+174/-24)
tests/unit/conftest.py (+52/-2)
tests/unit/test_lib.py (+121/-14)
tests/unit/test_libdb.py (+66/-0)
tox.ini (+1/-0)
Reviewer Review Type Date Requested Status
Xav Paice (community) Needs Fixing
Stuart Bishop (community) Approve
Chris Sanders (community) Needs Fixing
BootStack Reviewers mr tracking; do not claim Pending
BootStack Reviewers Pending
BootStack Reviewers Pending
Alex Kavanagh Pending
Andrew McLeod Pending
Review via email: mp+369605@code.launchpad.net

Description of the change

* lint: passed (make lint)
* unit tests: 30 passed, 19 warnings in 1.17 seconds (make unittest)
* Func tests: 13 passed (a bootstrapped controller needs to exist; functest-grafana model will be created or used, if it already exists) - (make functional) - py36+ needs to be used

Both snap and apt install_methods are tested, as well as a fake install method ("wrong-method") to verify that the unit gets blocked.

To post a comment you must log in.
715d850... by Alvaro Uria

Func tests: Render the overlay template

a360580... by Alvaro Uria

Fix wrong install_method output

Revision history for this message
Chris Sanders (chris.sanders) wrote :

A few comments inline. I notice that the git-log correctly states that unit testing are being run from the built charm directory. I'm curious that I don't see that change in this MR, maybe it was already that way? The git log doesn't seem to support that, are there changes that are somehow introduced and not shown on the MR?

Back to the unit testing. Charm building can be quite slow, unit testing needs to be very fast and those are at odds. Requiring a charm-build before a unit test will take the test time for unit tests from several seconds (or less) to several minutes (or more) per run. Whatever dependencies are causing this should be mocked instead.

If the dependency issue is another layer, the template includes an example of patching charm layers and options on charm layers. The unit testing shouldn't extend to testing code in included layers so mocking is sufficient.

A second item I noticed when looking at the repository. Several of these files do an import, catch, and apt install. That really should not happen, dependencies should be installed with the install hook not at run time during an import.

review: Needs Fixing
Revision history for this message
Stuart Bishop (stub) wrote :

Seems fine from my POV. Code looks fine. Mostly adding tests, which I can't really comment on as they are using what is essentially now a proprietary charm test framework and quite alien. The intents seem fine, but I can't comment on correctness or recommend alternate approaches. The extent of new tests will likely cement this as a BootStack maintained charm. Over here we seem to be proceeding with the plan to use zaza.

Unrelated to this particular branch, I think the charm would benefit from using layer:status. Reasoning about status messages becomes much easier, with layer:status buffering them to ensure that the most important one is what is presented to the end user and you stop needing to worry about a 'maintenance' message obliterating an important 'blocked' or 'active'. It should just drop in, monkey patching hookenv.status_set if you don't want to switch to the slightly nicer call syntax.

Also unrelated to this particular branch, as charms are supposed to encode best practice, the charm should really decide what that is and only support one installation method. This of course might be difficult for an upgraded deployment, but it would be good if it could be considered a goal. Choice here means more complexity (like this test suite, needing to run many more combinations of options), and a good proportion of installations we need to support not being best practice.

review: Approve
Revision history for this message
Xav Paice (xavpaice) wrote :
review: Needs Fixing

Unmerged commits

a360580... by Alvaro Uria

Fix wrong install_method output

ea07818... by Nicolas Pochet

Move to new default repository

* According to Grafana's documentation, one needs to use the new
repository.
* This change replaces the old repository and key by the new ones.

Closes-Bug: 1814303

715d850... by Alvaro Uria

Func tests: Render the overlay template

bdc7c06... by Alvaro Uria

Func test to verify if dashboards were imported

7611dba... by Alvaro Uria

Add initial func tests

86ac5e1... by Alvaro Uria

Start adding lib_grafana.db unit tests

1e11f71... by Alvaro Uria

Add more unit tests on the helpers lib

3193cee... by Alvaro Uria

Add extensive unit test coverage

5bd2d0d... by Alvaro Uria

Unit tests: remove unittest.mock usage (only pytest)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 6a23b4b..d5218f7 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -20,3 +20,7 @@ repo-info
6
7 # reports
8 reports/*
9+
10+# included layers or interfaces
11+interfaces/
12+layers/
13diff --git a/actions/grafana_utils.py b/actions/grafana_utils.py
14index 0079dec..dfd4f57 100644
15--- a/actions/grafana_utils.py
16+++ b/actions/grafana_utils.py
17@@ -1,4 +1,4 @@
18-#!/usr/bin/python3
19+#!/usr/local/sbin/charm-env python3
20
21 from charmhelpers.core.hookenv import (
22 config,
23diff --git a/config.yaml b/config.yaml
24index 5db3f1a..c4dcd96 100644
25--- a/config.yaml
26+++ b/config.yaml
27@@ -1,7 +1,7 @@
28 options:
29 install_sources:
30 default: |
31- - 'deb https://packagecloud.io/grafana/stable/debian/ stretch main'
32+ - 'deb https://packages.grafana.com/oss/deb stable main'
33 type: string
34 description: |
35 YAML list of additional installation sources, as a string. The number of
36@@ -13,68 +13,34 @@ options:
37 default: |
38 - |
39 -----BEGIN PGP PUBLIC KEY BLOCK-----
40- Version: GnuPG v1.4.11 (GNU/Linux)
41+ Version: GnuPG v1
42
43- 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
102- =ewdt
103+ mQENBFiHXVIBCADr3VDEAGpq9Sg/xrPVu1GGqWGXdbnTbbNKeveCtFHZz7/GSATW
104+ iwiY1skvlAOBiIKCqJEji0rZZgd8WxuhdfugiCBk1hDTMWCpjI0P+YymV77jHjYB
105+ jHrKNlhb+aLjEd9Gf2EtbKUT1fvGUkzlVrcRGSX/XR9MBZlgja7NIyuVbn3uwZQ4
106+ jflWSNSlvMpohNxTFkrBFTRrCJXhbDLfCS46+so22CP3+1VQyqJ7/6RWK9v9KYdS
107+ AVNgILXMggSrMqha4WA1a/ktczVQXNtP8IuPxTdp9pNYsklOTmrFVeq3mXsvWh9Q
108+ lIhpYHIZlTZ5wVBq4wTRchsXC5MubIhz+ASDABEBAAG0GkdyYWZhbmEgPGluZm9A
109+ Z3JhZmFuYS5jb20+iQE4BBMBAgAiBQJYh11SAhsDBgsJCAcDAgYVCAIJCgsEFgID
110+ AQIeAQIXgAAKCRCMjDTFJAmMthxJB/9Id6JrwqRkJW+eSBb71FGQmRsJvNFR8J+3
111+ NPVhJNkTFFOM7TnjAMUIv+LYEURqGcceTNAN1aHq/7n/8ybXucCS0CnDYyNYpyVs
112+ tWJ3FOQK3jPrmziDCWPQATqMM/Z2auXVFWrDFqfh2xKZNjuix0w2nyuWB8U0CG2U
113+ 89w+ksPJblGGU5xLPPzDQoAqyZXY3gpGGTkCuohMq2RWYbp/QJSQagYhQkKZoJhr
114+ XJlnw4At6R1A5UUPzDw6WJqMRkGrkieE6ApIgf1vZSmnLRpXkqquRTAEyGT8Pugg
115+ ee6YkD19/LK6ED6gn32StY770U9ti560U7oRjrOPK/Kjp4+qBtkQuQENBFiHXVIB
116+ CACz4hO1g/4fKO9QWLcbSWpB75lbNgt1kHXP0UcW8TE0DIgqrifod09lC85adIz0
117+ zdhs+00lLqckM5wNbp2r+pd5rRaxOsMw2V+c/y1Pt3qZxupmPc5l5lL6jzbEVR9g
118+ ygPaE+iabTk9Np2OZQ7Qv5gIDzivqK2mRHXaHTzoQn2dA/3xpFcxnen9dvu7LCpA
119+ CdScSj9/UIRKk9PHIgr2RJhcjzLx0u1PxN9MEqfIsIJUUgZOoDsr8oCs44PGGIMm
120+ cK1CKALLLiC4ZM58B56jRyXo18MqB6VYsC1X9wkcIs72thL3tThXO70oDGcoXzoo
121+ ywAHBH63EzEyduInOhecDIKlABEBAAGJAR8EGAECAAkFAliHXVICGwwACgkQjIw0
122+ xSQJjLbWSwf/VIM5wEFBY4QLGUAfqfjDyfGXpcha58Y24Vv3n6MwJqnCIbTAaeWf
123+ 30CZ/wHg3NNIMB7I31vgmMOEbHQdv0LPTi9TG205VQeehcpNtZRZQ0D8TIetbxyi
124+ Emmn9osig9U3/7jaAWBabE/9bGx4TF3eLlEH9wmFrNYeXvgRqmyqVoqhIMCNAAOY
125+ REYyHyy9mzr9ywkwl0aroBqhzKIPyFlatZy9oRKllY/CCKO9RJy4DZidLphuwzqU
126+ ymdQ1sqe5nKvwG5GvcncPc3O7LMevDBWnpNNkgERnVxCqpm90TuE3ONbirnU4+/S
127+ tUsVU1DERc1fjOCnAm4pKIlNYphISIE7OQ==
128+ =0pMC
129 -----END PGP PUBLIC KEY BLOCK-----
130 type: string
131 description: |
132diff --git a/lib/lib_grafana.py b/lib/lib_grafana.py
133deleted file mode 100644
134index 8a4405f..0000000
135--- a/lib/lib_grafana.py
136+++ /dev/null
137@@ -1,43 +0,0 @@
138-from charmhelpers.core import hookenv, host, unitdata
139-from charms.reactive import remove_state
140-
141-
142-APT_COMMON = '/var/lib/grafana'
143-SNAP_NAME = 'grafana'
144-SNAP_COMMON = '/var/snap/{}/common/data'.format(SNAP_NAME)
145-
146-
147-class GrafanaHelper():
148- def __init__(self):
149- self.charm_config = hookenv.config()
150-
151- @property
152- def password(self):
153- if self.charm_config.get('admin_password', False):
154- return self.charm_config.get('admin_password')
155-
156- kv = unitdata.kv()
157- if kv.get('grafana.admin_password', False):
158- return kv.get('grafana.admin_password')
159-
160- passwd = host.pwgen(16)
161- kv.set('grafana.admin_password', passwd)
162- return passwd
163-
164- @property
165- def nagios_context(self):
166- nag_ctxt = self.charm_config.get('nagios_context', False)
167- if not nag_ctxt:
168- nag_ctxt = 'UNKNOWN'
169- return nag_ctxt
170-
171-
172-def data_path():
173- data_dir = {'snap': SNAP_COMMON, 'apt': APT_COMMON}
174- kv = unitdata.kv()
175- source = kv.get('install_method')
176- if source in ('snap', 'apt'):
177- return data_dir[source]
178- else:
179- hookenv.status_set('blocked', 'Unsupported install_method')
180- remove_state('grafana.installed')
181diff --git a/lib/lib_grafana/db.py b/lib/lib_grafana/db.py
182index ad8a573..4abe3fd 100644
183--- a/lib/lib_grafana/db.py
184+++ b/lib/lib_grafana/db.py
185@@ -149,7 +149,7 @@ class DbManagement():
186 stmt = ('UPDATE DATA_SOURCE SET basic_auth_user = ?,'
187 ' basic_auth_password = ?, basic_auth = 0')
188 values = ('', '')
189- return (stmt, values)
190+ return [stmt, values]
191
192 def delete_from_datasource(self, type, url):
193 query = 'DELETE FROM DATA_SOURCE WHERE type=? AND url=?'
194diff --git a/lib/lib_grafana/helpers.py b/lib/lib_grafana/helpers.py
195index eb2ecfb..8e258d8 100644
196--- a/lib/lib_grafana/helpers.py
197+++ b/lib/lib_grafana/helpers.py
198@@ -306,9 +306,13 @@ class GrafanaHelper():
199 # Since there are deployments configured before the SNAP support, we need
200 # to leave APT as the default install_method
201 source = self.charm_config.get('install_method', 'apt')
202+ if source not in ('snap', 'apt'):
203+ raise GrafanaBlockedError('Unsupported install_method')
204+
205 kv = unitdata.kv()
206 if not kv.get('install_method', False):
207 kv.set('install_method', source)
208+
209 elif kv.get('install_method') != source:
210 raise GrafanaBlockedError(
211 'install_method changes unsupported,'
212diff --git a/reactive/grafana.py b/reactive/grafana.py
213index 17eda8d..fa5969c 100644
214--- a/reactive/grafana.py
215+++ b/reactive/grafana.py
216@@ -22,7 +22,7 @@ def install_packages():
217 try:
218 source = helper.verify_install_method()
219 except GrafanaBlockedError as error:
220- hookenv.status_set('blocked', error)
221+ hookenv.status_set('blocked', str(error))
222 return
223
224 if source == 'apt':
225@@ -34,10 +34,8 @@ def install_packages():
226 # NOTE(aluria): precise is the last supported Ubuntu release, so
227 # anything below 'p' is actually newer than xenial (systemd support)
228 helper.install_method_snap()
229- hookenv.status_set('blocked', 'Missing relations: grafana-source')
230- else:
231- hookenv.status_set('blocked', 'Unsupported install_method')
232- return
233+
234+ hookenv.status_set('blocked', 'Missing relations: grafana-source')
235 set_flag('grafana.installed')
236
237
238@@ -47,7 +45,7 @@ def install_plugins():
239 try:
240 plugins_dir = helper.plugins_dir
241 except GrafanaBlockedError as error:
242- hookenv.status_set('blocked', error)
243+ hookenv.status_set('blocked', str(error))
244 clear_flag('grafana.installed')
245 return
246
247@@ -90,7 +88,7 @@ def setup_grafana():
248 clear_flag('grafana.started')
249 hookenv.status_set('active', 'Completed configuring grafana')
250 except GrafanaBlockedError as error:
251- hookenv.status_set('blocked', error)
252+ hookenv.status_set('blocked', str(error))
253
254
255 @when('grafana.started')
256@@ -122,7 +120,7 @@ def restart_grafana():
257 hookenv.status_set('active', 'Grafana is running')
258 set_flag('grafana.started')
259 except GrafanaBlockedError as error:
260- hookenv.status_set('blocked', error)
261+ hookenv.status_set('blocked', str(error))
262
263
264 @when('grafana.started')
265@@ -134,7 +132,7 @@ def update_nrpe_config(svc):
266 try:
267 helper.update_nrpe_config()
268 except GrafanaBlockedError as error:
269- hookenv.status_set('blocked', error)
270+ hookenv.status_set('blocked', str(error))
271
272
273 @when_not('nrpe-external-master.available')
274@@ -187,7 +185,7 @@ def sources_gone(relation):
275 hookenv.log('Removing datasource: {}'.format(ds))
276
277 if ds:
278- db.delete_from_datasource()
279+ db.delete_from_datasource(ds['type'], ds['url'])
280 relation.set_local = None
281
282
283diff --git a/tests/functional/bundle.yaml b/tests/functional/bundle.yaml
284new file mode 100644
285index 0000000..381d91d
286--- /dev/null
287+++ b/tests/functional/bundle.yaml
288@@ -0,0 +1,189 @@
289+series: bionic
290+applications:
291+ ceph-mon:
292+ annotations:
293+ gui-x: '750'
294+ gui-y: '500'
295+ charm: cs:ceph-mon
296+ num_units: 3
297+ options:
298+ expected-osd-count: 3
299+ ceph-osd:
300+ annotations:
301+ gui-x: '1000'
302+ gui-y: '500'
303+ charm: cs:ceph-osd
304+ num_units: 3
305+ options:
306+ osd-devices: /srv/osd
307+ use-direct-io: False
308+ bluestore: False
309+ cinder:
310+ annotations:
311+ gui-x: '750'
312+ gui-y: '0'
313+ charm: cs:cinder
314+ num_units: 1
315+ options:
316+ worker-multiplier: 0.1
317+ block-device: None
318+ glance-api-version: 2
319+ cinder-ceph:
320+ annotations:
321+ gui-x: '750'
322+ gui-y: '250'
323+ charm: cs:cinder-ceph
324+ num_units: 0
325+ glance:
326+ annotations:
327+ gui-x: '250'
328+ gui-y: '0'
329+ charm: cs:glance
330+ num_units: 1
331+ options:
332+ worker-multiplier: 0.1
333+ keystone:
334+ annotations:
335+ gui-x: '500'
336+ gui-y: '0'
337+ charm: cs:keystone
338+ num_units: 1
339+ options:
340+ worker-multiplier: 0.1
341+ mysql:
342+ annotations:
343+ gui-x: '0'
344+ gui-y: '250'
345+ charm: cs:percona-cluster
346+ num_units: 1
347+ options:
348+ max-connections: 1000
349+ innodb-buffer-pool-size: 256M
350+ tuning-level: fast
351+ neutron-api:
352+ annotations:
353+ gui-x: '500'
354+ gui-y: '500'
355+ charm: cs:neutron-api
356+ num_units: 1
357+ options:
358+ worker-multiplier: 0.1
359+ neutron-security-groups: true
360+ overlay-network-type: "gre vxlan"
361+ flat-network-providers: physnet1
362+ neutron-openvswitch:
363+ annotations:
364+ gui-x: '250'
365+ gui-y: '500'
366+ charm: cs:neutron-openvswitch
367+ num_units: 0
368+ nova-cloud-controller:
369+ annotations:
370+ gui-x: '0'
371+ gui-y: '500'
372+ charm: cs:nova-cloud-controller
373+ num_units: 1
374+ options:
375+ worker-multiplier: 0.1
376+ network-manager: Neutron
377+ ram-allocation-ratio: '64'
378+ cpu-allocation-ratio: '64'
379+ nova-compute:
380+ annotations:
381+ gui-x: '250'
382+ gui-y: '250'
383+ charm: cs:nova-compute
384+ num_units: 1
385+ options:
386+ enable-live-migration: False
387+ enable-resize: False
388+ migration-auth-type: ssh
389+ force-raw-images: False
390+ rabbitmq-server:
391+ annotations:
392+ gui-x: '500'
393+ gui-y: '250'
394+ charm: cs:rabbitmq-server
395+ num_units: 1
396+
397+ prometheus:
398+ charm: cs:prometheus2
399+ num_units: 1
400+ options:
401+ label-juju-units: true
402+ telegraf:
403+ charm: cs:telegraf
404+ options:
405+ hostname: '{unit}'
406+ prometheus_output_port: default
407+ prometheus-ceph-exporter:
408+ charm: cs:prometheus-ceph-exporter
409+ num_units: 1
410+
411+relations:
412+- - nova-compute:ceph-access
413+ - cinder-ceph:ceph-access
414+- - nova-compute:amqp
415+ - rabbitmq-server:amqp
416+- - keystone:shared-db
417+ - mysql:shared-db
418+- - nova-cloud-controller:identity-service
419+ - keystone:identity-service
420+- - glance:identity-service
421+ - keystone:identity-service
422+- - neutron-api:identity-service
423+ - keystone:identity-service
424+- - neutron-openvswitch:neutron-plugin-api
425+ - neutron-api:neutron-plugin-api
426+- - neutron-api:shared-db
427+ - mysql:shared-db
428+- - neutron-api:amqp
429+ - rabbitmq-server:amqp
430+- - glance:shared-db
431+ - mysql:shared-db
432+- - glance:amqp
433+ - rabbitmq-server:amqp
434+- - nova-cloud-controller:image-service
435+ - glance:image-service
436+- - nova-compute:image-service
437+ - glance:image-service
438+- - nova-cloud-controller:cloud-compute
439+ - nova-compute:cloud-compute
440+- - nova-cloud-controller:amqp
441+ - rabbitmq-server:amqp
442+- - nova-compute:neutron-plugin
443+ - neutron-openvswitch:neutron-plugin
444+- - neutron-openvswitch:amqp
445+ - rabbitmq-server:amqp
446+- - nova-cloud-controller:shared-db
447+ - mysql:shared-db
448+- - nova-cloud-controller:neutron-api
449+ - neutron-api:neutron-api
450+- - cinder:image-service
451+ - glance:image-service
452+- - cinder:amqp
453+ - rabbitmq-server:amqp
454+- - cinder:identity-service
455+ - keystone:identity-service
456+- - cinder:cinder-volume-service
457+ - nova-cloud-controller:cinder-volume-service
458+- - cinder-ceph:storage-backend
459+ - cinder:storage-backend
460+- - ceph-mon:client
461+ - nova-compute:ceph
462+- - cinder:shared-db
463+ - mysql:shared-db
464+- - ceph-mon:client
465+ - cinder-ceph:ceph
466+- - ceph-mon:client
467+ - glance:ceph
468+- - ceph-osd:mon
469+ - ceph-mon:osd
470+- - prometheus-ceph-exporter
471+ - ceph-mon:client
472+- - prometheus:target
473+ - telegraf:prometheus-client
474+- - prometheus:target
475+ - prometheus-ceph-exporter
476+- - telegraf
477+ - ceph-mon
478diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py
479index dac4c73..e0f6205 100644
480--- a/tests/functional/conftest.py
481+++ b/tests/functional/conftest.py
482@@ -1,4 +1,3 @@
483-#!/usr/bin/python3
484 '''
485 Reusable pytest fixtures for functional testing
486
487@@ -14,10 +13,10 @@ import json
488 import os
489 import subprocess
490 import uuid
491+
492 import juju
493 from juju.controller import Controller
494 from juju.errors import JujuError
495-
496 import pytest
497
498 STAT_CMD = '''python3 - <<EOF
499diff --git a/tests/functional/overlay.yaml.j2 b/tests/functional/overlay.yaml.j2
500new file mode 100644
501index 0000000..cab782a
502--- /dev/null
503+++ b/tests/functional/overlay.yaml.j2
504@@ -0,0 +1,11 @@
505+applications:
506+ {{ appname }}:
507+ series: {{ series }}
508+ charm: {{ charm_path }}
509+ num_units: 1
510+ options:
511+ install_method: {{ install_method }}
512+
513+relations:
514+- - prometheus:grafana-source
515+ - {{ appname }}:grafana-source
516diff --git a/tests/functional/requirements.txt b/tests/functional/requirements.txt
517index f76bfbb..b2e6ce6 100644
518--- a/tests/functional/requirements.txt
519+++ b/tests/functional/requirements.txt
520@@ -1,4 +1,5 @@
521 flake8
522+Jinja2
523 juju
524 mock
525 pytest
526diff --git a/tests/functional/test_deploy.py b/tests/functional/test_deploy.py
527index ea7436e..47794ef 100644
528--- a/tests/functional/test_deploy.py
529+++ b/tests/functional/test_deploy.py
530@@ -1,50 +1,200 @@
531 import os
532+import subprocess
533+
534+import asyncio
535+import json
536 import pytest
537+import requests
538+import yaml
539+import jinja2
540
541 # Treat all tests as coroutines
542 pytestmark = pytest.mark.asyncio
543
544-series = ['xenial', 'bionic']
545-charm_build_dir = os.getenv('CHARM_BUILD_DIR', '.').rstrip('/')
546+INSTALL_METHOD = ['apt', 'snap', 'wrong-method']
547+FAKE_INSTALL_METHOD = 'wrong-method'
548+SERIES = ['xenial', 'bionic']
549+CHARM_BUILD_DIR = os.getenv('CHARM_BUILD_DIR', '.').rstrip('/')
550+
551+################
552+# Helper funcs #
553+################
554+
555
556+def render(templates_dir, template_name, context):
557+ templates = jinja2.Environment(
558+ loader=jinja2.FileSystemLoader(templates_dir))
559+ template = templates.get_template(template_name)
560+ return template.render(context)
561
562 ###################
563 # Custom fixtures #
564 ###################
565
566
567-@pytest.fixture
568-async def apps(model):
569- apps = []
570- for entry in series:
571- app = model.applications['grafana-{}'.format(entry)]
572- apps.append(app)
573+@pytest.fixture(scope='module', params=SERIES)
574+async def apps(request, model):
575+ series = request.param
576+ apps = model.applications.get('grafana-{}'.format(series))
577 return apps
578
579
580-@pytest.fixture
581-async def units(apps):
582- units = []
583- for app in apps:
584- units.extend(app.units)
585- return units
586+@pytest.fixture(scope='module', params=SERIES)
587+async def units(request, apps):
588+ return apps.units
589
590
591-@pytest.mark.parametrize('series', series)
592-async def test_grafana_deploy(model, series):
593- # Starts a deploy for each series
594- await model.deploy('{}/grafana'.format(charm_build_dir), series=series,
595- application_name='grafana-{}'.format(series))
596- assert True
597+@pytest.fixture(scope='module')
598+async def model(controller):
599+ model_name = 'functest-grafana'
600+ if model_name not in await controller.list_models():
601+ _model = await controller.add_model(model_name,
602+ cloud_name=os.getenv('PYTEST_CLOUD_NAME'),
603+ region=os.getenv('PYTEST_CLOUD_REGION'),
604+ )
605+ else:
606+ _model = await controller.get_model(model_name)
607+
608+ # https://github.com/juju/python-libjuju/issues/267
609+ subprocess.check_call(['juju', 'models'])
610+ while model_name not in await controller.list_models():
611+ await asyncio.sleep(1)
612+ yield _model
613+ await _model.disconnect()
614+
615+
616+@pytest.fixture(scope='module')
617+async def deploy_openstack(model):
618+ dir_path = os.path.dirname(os.path.realpath(__file__))
619+ bundle_path = os.path.join(dir_path, 'bundle.yaml')
620+
621+ if os.path.exists(bundle_path):
622+ # Note(aluria): using await model.deploy(bundle_path) will raise an error
623+ # because ceph-mon application already exists and cannot be added
624+ # OTOH, 'juju deploy bundle.yaml' will try to finalize a previous run
625+ subprocess.check_call([
626+ 'juju',
627+ 'deploy',
628+ '-m',
629+ model.info.name,
630+ bundle_path,
631+ ])
632+ assert True
633+ else:
634+ assert False
635+
636+
637+@pytest.fixture(scope='module',
638+ params=[[series, install_method]
639+ for series in SERIES for install_method in INSTALL_METHOD],
640+ ids=['{}-{}'.format(series, install_method)
641+ for series in SERIES for install_method in INSTALL_METHOD])
642+async def deploy_app(request, model):
643+ series, install_method = request.param
644+ app_name = 'grafana-{series}-{imethod}'.format(series=series, imethod=install_method)
645+ dir_path = os.path.dirname(os.path.realpath(__file__))
646+ bundle_path = os.path.join(dir_path, 'bundle.yaml')
647+ overlay_path = os.path.join(CHARM_BUILD_DIR, 'overlay.yaml')
648+ context = {
649+ 'appname': app_name,
650+ 'series': series,
651+ 'install_method': install_method,
652+ 'charm_path': os.path.join(CHARM_BUILD_DIR, 'grafana'),
653+ }
654+ # render overlay config file
655+ rendered = render(dir_path, 'overlay.yaml.j2', context)
656+ with open(overlay_path, 'w') as fd:
657+ fd.write(rendered)
658
659+ subprocess.check_call([
660+ 'juju',
661+ 'deploy',
662+ '-m',
663+ model.info.name,
664+ bundle_path,
665+ '--overlay',
666+ overlay_path,
667+ ])
668+ while True:
669+ try:
670+ grafana_app = model.applications[app_name]
671+ break
672+ except KeyError:
673+ await asyncio.sleep(5)
674+ yield grafana_app
675
676 #########
677 # Tests #
678 #########
679
680
681-async def test_grafana_status(apps, model):
682- # Verifies status for all deployed series of the charm
683- for app in apps:
684- await model.block_until(lambda: app.status == 'active')
685+async def test_openstack_deploy(deploy_openstack, model):
686+ dir_path = os.path.dirname(os.path.realpath(__file__))
687+ bundle_path = os.path.join(dir_path, 'bundle.yaml')
688+ with open(bundle_path, 'r') as fd:
689+ num_apps = len(yaml.safe_load(fd)["applications"].keys())
690+
691+ await model.block_until(lambda: (len(model.applications) >= num_apps
692+ and all(app.status == 'active'
693+ for name, app in model.applications.items()
694+ if name.find(FAKE_INSTALL_METHOD) == -1)),
695+ timeout=3600)
696+ assert True
697+
698+
699+async def test_grafana_status(deploy_app, model):
700+ config = await deploy_app.get_config()
701+ install_method = config['install_method']['value']
702+ if install_method == FAKE_INSTALL_METHOD:
703+ status = 'blocked'
704+ message = 'Unsupported install_method'
705+ else:
706+ status = 'active'
707+ message = 'Grafana is running'
708+
709+ await model.block_until(lambda: (deploy_app.status == status
710+ and all(unit.workload_status_message == message
711+ for unit in deploy_app.units)),
712+ timeout=900)
713 assert True
714+
715+
716+async def test_grafana_imported_dashboards(deploy_app, model):
717+ config = await deploy_app.get_config()
718+ install_method = config['install_method']['value']
719+ if install_method == FAKE_INSTALL_METHOD:
720+ status = 'blocked'
721+ message = 'Unsupported install_method'
722+ else:
723+ status = 'active'
724+ message = 'Grafana is running'
725+
726+ port = config['port']['value']
727+ units = deploy_app.units
728+ for grafana_unit in units:
729+ await model.block_until(lambda: (grafana_unit.workload_status == status
730+ and grafana_unit.agent_status == 'idle'
731+ and grafana_unit.workload_status_message == message),
732+ timeout=900)
733+ if install_method == FAKE_INSTALL_METHOD:
734+ # no further checks
735+ continue
736+
737+ action = await grafana_unit.run_action('get-admin-password')
738+ action = await action.wait()
739+ passwd = action.results['password']
740+ host = grafana_unit.public_address
741+ req = requests.get('http://{host}:{port}'
742+ '/api/search?dashboardIds'.format(host=host, port=port),
743+ auth=('admin', passwd))
744+ if req.status_code == 200:
745+ dashboards = json.loads(req.text)
746+ if len(dashboards) >= 3:
747+ assert all(dash.get('type', '') == 'dash-db'
748+ and dash.get('uri', '').startswith('db/juju-')
749+ for dash in dashboards)
750+ continue
751+ assert len(dashboards) >= 3
752+ assert req.status_code == 200
753+
754+ assert len(units) > 0
755diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
756index b7c019d..18273ef 100644
757--- a/tests/unit/conftest.py
758+++ b/tests/unit/conftest.py
759@@ -1,8 +1,10 @@
760-#!/usr/bin/python3
761+import datetime
762 import unittest.mock as mock
763
764 import pytest
765
766+import lib_grafana
767+
768
769 # If layer options are used, add this to grafana
770 # and import layer in lib_grafana
771@@ -44,6 +46,28 @@ def mock_hookenv_config(monkeypatch):
772
773
774 @pytest.fixture
775+def mock_unitdata_admin_password(monkeypatch):
776+ class TestUnitdataAdminPwd(dict):
777+ def __init__(self, d={}):
778+ self._d = d
779+
780+ def __get__(self, name):
781+ return self._d[name]
782+
783+ def get(self, name, altvalue=None):
784+ return self._d.get(name, altvalue)
785+
786+ def set(self, name, value):
787+ self._d[name] = value
788+
789+ def _unitdata_admin_password(arg={}):
790+ obj = TestUnitdataAdminPwd(arg)
791+ return obj
792+
793+ return _unitdata_admin_password
794+
795+
796+@pytest.fixture
797 def mock_remote_unit(monkeypatch):
798 monkeypatch.setattr('lib_grafana.helpers.hookenv.remote_unit', lambda: 'unit-mock/0')
799
800@@ -55,7 +79,7 @@ def mock_charm_dir(monkeypatch):
801
802
803 @pytest.fixture
804-def grafana(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch):
805+def grafana_helper(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch):
806 from lib_grafana.helpers import GrafanaHelper
807 helper = GrafanaHelper()
808
809@@ -63,3 +87,29 @@ def grafana(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch):
810 monkeypatch.setattr('lib_grafana.helpers.GrafanaHelper', lambda: helper)
811
812 return helper
813+
814+
815+@pytest.fixture
816+def grafana_db(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch):
817+ class TestSqlite3(object):
818+ def cursor(cls):
819+ pass
820+
821+ def close(cls):
822+ pass
823+
824+ class NewDate(datetime.datetime):
825+ @classmethod
826+ def today(cls):
827+ return cls(1971, 1, 1)
828+
829+ from lib_grafana.db import DbManagement
830+
831+ monkeypatch.setattr('lib_grafana.db.sqlite3.connect', lambda filename, timeout: TestSqlite3())
832+ monkeypatch.setattr('lib_grafana.db.hookenv.log', lambda message, level=None: None)
833+ helper = DbManagement(lib_grafana.SNAP_COMMON)
834+ # Any other functions that load helper will get this version
835+ monkeypatch.setattr('lib_grafana.db.DbManagement', lambda: helper)
836+ monkeypatch.setattr('lib_grafana.db.datetime.datetime', NewDate)
837+
838+ return helper
839diff --git a/tests/unit/test_lib.py b/tests/unit/test_lib.py
840index 8cba983..bb799ce 100644
841--- a/tests/unit/test_lib.py
842+++ b/tests/unit/test_lib.py
843@@ -1,20 +1,36 @@
844-#!/usr/bin/python3
845-import unittest.mock as mock
846-
847 import pytest
848
849 import lib_grafana
850 import lib_grafana.helpers
851
852
853-@pytest.mark.parametrize('admin_password', 'password')
854-def test_grafana(admin_password, grafana):
855+def test_grafana_constants(grafana_helper):
856 """See if the helper fixture works to load charm configs"""
857- grafana.charm_config['admin_password'] = admin_password
858+ assert isinstance(grafana_helper.charm_config, dict)
859+ assert grafana_helper.nagios_context == 'juju'
860+ assert grafana_helper.snap_name == 'grafana'
861+ assert grafana_helper.plugins_exist == ''
862+
863+
864+def test_admin_password_config(grafana_helper):
865+ admin_password = 'password'
866+ grafana_helper.charm_config['admin_password'] = admin_password
867+ assert grafana_helper.password == admin_password
868+
869+
870+def test_admin_password_unitdata(monkeypatch, grafana_helper):
871+ admin_password = 'password'
872+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
873+ lambda: {'grafana.admin_password': admin_password})
874+ assert grafana_helper.password == admin_password
875+
876
877- assert isinstance(grafana.charm_config, dict)
878- assert grafana.password == admin_password
879- assert grafana.nagios_context == 'juju'
880+def test_admin_password_pwgen(monkeypatch, mock_unitdata_admin_password, grafana_helper):
881+ admin_password = 'password'
882+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
883+ mock_unitdata_admin_password)
884+ monkeypatch.setattr('lib_grafana.helpers.host.pwgen', lambda n: admin_password)
885+ assert grafana_helper.password == admin_password
886
887
888 @pytest.mark.parametrize(
889@@ -25,12 +41,103 @@ def test_grafana(admin_password, grafana):
890 ('wrong_method', lib_grafana.helpers.GrafanaBlockedError),
891 ]
892 )
893-@mock.patch('lib_grafana.helpers.unitdata.kv')
894-def test_data_path(mock_unitdata, install_method, result, grafana):
895- mock_unitdata.return_value = {'install_method': install_method}
896+def test_data_path(install_method, result, monkeypatch, grafana_helper):
897+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
898+ lambda: {'install_method': install_method})
899+ if install_method != 'wrong_method':
900+ assert grafana_helper.data_path == result
901+ else:
902+ with pytest.raises(result) as excinfo:
903+ grafana_helper.data_path
904+ assert str(excinfo.value) == 'Unsupported install_method'
905+
906+
907+@pytest.mark.parametrize(
908+ 'install_method,result',
909+ [
910+ ('apt', '/etc/grafana/grafana.ini'),
911+ ('snap', '{}/conf/grafana.ini'.format(lib_grafana.SNAP_DATA)),
912+ ('wrong_method', lib_grafana.helpers.GrafanaBlockedError),
913+ ]
914+)
915+def test_grafana_ini(install_method, result, monkeypatch, grafana_helper):
916+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
917+ lambda: {'install_method': install_method})
918 if install_method != 'wrong_method':
919- assert grafana.data_path == result
920+ assert grafana_helper.grafana_ini == result
921 else:
922 with pytest.raises(result) as excinfo:
923- grafana.data_path
924+ grafana_helper.grafana_ini
925 assert str(excinfo.value) == 'Unsupported install_method'
926+
927+
928+@pytest.mark.parametrize(
929+ 'install_method,result',
930+ [
931+ ('apt', 'apt'),
932+ ('snap', 'snap'),
933+ ('wrong_method', lib_grafana.helpers.GrafanaBlockedError),
934+ ]
935+)
936+def test_source(install_method, result, monkeypatch, grafana_helper):
937+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
938+ lambda: {'install_method': install_method})
939+ if install_method != 'wrong_method':
940+ assert grafana_helper.source == result
941+ else:
942+ with pytest.raises(result) as excinfo:
943+ grafana_helper.data_path
944+ assert str(excinfo.value) == 'Unsupported install_method'
945+
946+
947+@pytest.mark.parametrize(
948+ 'datasources,result',
949+ [
950+ (False, False),
951+ ('type,name,access,url,password,user,database', False),
952+ ('prometheus,name,proxy,url,password,user,database', True),
953+ ('prometheus,name,proxy,missing_elements', False)
954+ ]
955+)
956+def test_validate_datasources(datasources, result, grafana_helper):
957+ grafana_helper.charm_config['datasources'] = datasources
958+ assert grafana_helper.validate_datasources() == result
959+
960+
961+@pytest.mark.parametrize(
962+ 'jujuconfig,stored,result',
963+ [
964+ ('apt', 'apt', 'apt'),
965+ ('apt', 'snap', lib_grafana.helpers.GrafanaBlockedError),
966+ ('snap', 'snap', 'snap'),
967+ ('snap', 'apt', lib_grafana.helpers.GrafanaBlockedError),
968+ (None, 'apt', 'apt'),
969+ (None, 'snap', lib_grafana.helpers.GrafanaBlockedError),
970+ ('wrong_method', None, lib_grafana.helpers.GrafanaBlockedError),
971+ ('wrong_method', 'apt', lib_grafana.helpers.GrafanaBlockedError),
972+ ]
973+)
974+def test_verify_install_method(jujuconfig, stored, result, mock_unitdata_admin_password,
975+ monkeypatch, grafana_helper):
976+ grafana_helper.charm_config['install_method'] = jujuconfig
977+ if jujuconfig is None:
978+ grafana_helper.charm_config['install_method'] = 'apt'
979+
980+ if stored is None:
981+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
982+ mock_unitdata_admin_password)
983+ else:
984+ monkeypatch.setattr('lib_grafana.helpers.unitdata.kv',
985+ lambda: mock_unitdata_admin_password({'install_method': stored}))
986+
987+ if isinstance(result, str):
988+ assert grafana_helper.verify_install_method() == result
989+ else:
990+ if jujuconfig == 'wrong_method':
991+ errormsg = 'Unsupported install_method'
992+ else:
993+ errormsg = ('install_method changes unsupported,'
994+ ' revert install_method config to previous value')
995+ with pytest.raises(result) as excinfo:
996+ grafana_helper.verify_install_method()
997+ assert str(excinfo.value) == errormsg
998diff --git a/tests/unit/test_libdb.py b/tests/unit/test_libdb.py
999new file mode 100644
1000index 0000000..006c8ed
1001--- /dev/null
1002+++ b/tests/unit/test_libdb.py
1003@@ -0,0 +1,66 @@
1004+import pytest
1005+
1006+
1007+@pytest.mark.parametrize(
1008+ 'ds,url,exists',
1009+ [
1010+ ('prometheus', 'http://10.10.20.20:9090', True),
1011+ ('prometheus', 'http://wrong.url', False),
1012+ ('wrong_ds', 'http://10.10.20.20:9090', False),
1013+ ('wrong_ds', 'http://wrong.url', False),
1014+ ]
1015+)
1016+def test_check_datasource(ds, url, exists, grafana_db):
1017+ datasource = {
1018+ 'service_name': 'prometheus2',
1019+ 'type': 'prometheus',
1020+ 'url': 'http://10.10.20.20:9090',
1021+ 'description': 'Juju generated source',
1022+ }
1023+ grafana_db.select_query = lambda x: [
1024+ [
1025+ 'item0', ds, 'item2', url, 'item4',
1026+ ],
1027+ ]
1028+ grafana_db.update_db = lambda x, y: None
1029+ if exists:
1030+ grafana_db.generate_query = lambda x, y, z: ['stmt', ['values']]
1031+ else:
1032+ grafana_db.generate_query = lambda x, y: ['stmt', ['values']]
1033+
1034+ grafana_db.check_datasource(datasource)
1035+ assert True
1036+
1037+
1038+def test_generate_query(monkeypatch, grafana_db):
1039+ date_str = '1971-01-01 00:00:00'
1040+ datasource = {
1041+ 'service_name': 'prometheus2',
1042+ 'type': 'prometheus',
1043+ 'url': 'http://10.10.20.20:9090',
1044+ 'description': 'Juju generated source',
1045+ }
1046+ expected_stmt = ('INSERT INTO DATA_SOURCE (org_id'
1047+ ', version'
1048+ ', type'
1049+ ', name'
1050+ ', access'
1051+ ', url'
1052+ ', is_default'
1053+ ', created'
1054+ ', updated'
1055+ ', basic_auth)'
1056+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
1057+ expected_values = [
1058+ 1,
1059+ 0,
1060+ 'prometheus',
1061+ 'prometheus2 - Juju generated source',
1062+ 'proxy',
1063+ 'http://10.10.20.20:9090',
1064+ 0,
1065+ date_str,
1066+ date_str,
1067+ 0,
1068+ ]
1069+ assert grafana_db.generate_query(datasource, 0) == [expected_stmt, expected_values]
1070diff --git a/tox.ini b/tox.ini
1071index 3a54b37..42cf001 100644
1072--- a/tox.ini
1073+++ b/tox.ini
1074@@ -41,5 +41,6 @@ exclude =
1075 .git,
1076 __pycache__,
1077 .tox,
1078+ layers,
1079 max-line-length = 120
1080 max-complexity = 10

Subscribers

People subscribed via source and target branches