Merge ~aluria/charm-grafana:bug/1822329-tests into ~prometheus-charmers/charm-grafana:rewrite
- Git
- lp:~aluria/charm-grafana
- bug/1822329-tests
- Merge into rewrite
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) |
||||
Related bugs: |
|
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 |
Commit message
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.
- 715d850... by Alvaro Uria
-
Func tests: Render the overlay template
- a360580... by Alvaro Uria
-
Fix wrong install_method output
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.
Xav Paice (xavpaice) wrote : | # |
Can this please be rebased against the https:/
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
1 | diff --git a/.gitignore b/.gitignore |
2 | index 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/ |
13 | diff --git a/actions/grafana_utils.py b/actions/grafana_utils.py |
14 | index 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, |
23 | diff --git a/config.yaml b/config.yaml |
24 | index 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: | |
132 | diff --git a/lib/lib_grafana.py b/lib/lib_grafana.py |
133 | deleted file mode 100644 |
134 | index 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') |
181 | diff --git a/lib/lib_grafana/db.py b/lib/lib_grafana/db.py |
182 | index 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=?' |
194 | diff --git a/lib/lib_grafana/helpers.py b/lib/lib_grafana/helpers.py |
195 | index 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,' |
212 | diff --git a/reactive/grafana.py b/reactive/grafana.py |
213 | index 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 | |
283 | diff --git a/tests/functional/bundle.yaml b/tests/functional/bundle.yaml |
284 | new file mode 100644 |
285 | index 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 |
478 | diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py |
479 | index 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 |
499 | diff --git a/tests/functional/overlay.yaml.j2 b/tests/functional/overlay.yaml.j2 |
500 | new file mode 100644 |
501 | index 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 |
516 | diff --git a/tests/functional/requirements.txt b/tests/functional/requirements.txt |
517 | index 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 |
526 | diff --git a/tests/functional/test_deploy.py b/tests/functional/test_deploy.py |
527 | index 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 |
755 | diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py |
756 | index 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 |
839 | diff --git a/tests/unit/test_lib.py b/tests/unit/test_lib.py |
840 | index 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 |
998 | diff --git a/tests/unit/test_libdb.py b/tests/unit/test_libdb.py |
999 | new file mode 100644 |
1000 | index 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] |
1070 | diff --git a/tox.ini b/tox.ini |
1071 | index 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 |
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.