Merge ~peter-sabaini/charm-grafana:implement-manual-upgrade into charm-grafana:master
- Git
- lp:~peter-sabaini/charm-grafana
- implement-manual-upgrade
- Merge into master
Status: | Merged |
---|---|
Merged at revision: | bf497246e7e94d501b0637fdb71106182cd93fd1 |
Proposed branch: | ~peter-sabaini/charm-grafana:implement-manual-upgrade |
Merge into: | charm-grafana:master |
Diff against target: |
620 lines (+304/-40) 12 files modified
README.md (+14/-7) actions.yaml (+3/-0) actions/do-upgrade (+63/-0) config.yaml (+4/-3) lib/charms/layer/grafana.py (+87/-1) reactive/grafana.py (+35/-17) tests/functional/tests/bundles/bionic-snap.yaml (+1/-0) tests/functional/tests/bundles/focal-snap.yaml (+1/-0) tests/functional/tests/test_grafana.py (+69/-2) tests/functional/tests/tests.yaml (+13/-8) tests/unit/test_grafana.py (+7/-1) tox.ini (+7/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Goins | Needs Fixing | ||
Review via email: mp+385281@code.launchpad.net |
Commit message
Upgrade support
Description of the change
Alvaro Uria (aluria) wrote : | # |
Alvaro Uria (aluria) wrote : | # |
In case we want to collect all channels and versions:
>>> dict([(channel[0], channel[1]) for channel in [i.strip().split() for i in subprocess.
{'latest/stable:': '6.7.4', 'latest/
Peter Sabaini (peter-sabaini) wrote : | # |
Hey, thanks for the valuable feedback, one question below though:
On 08.06.20 17:55, Alvaro Uria wrote:
> I've added a few comments inline
>
> Diff comments:
>
>> diff --git a/lib/charms/
>> index 1ddd431..bcc7977 100644
>> --- a/lib/charms/
>> +++ b/lib/charms/
>> @@ -90,3 +99,54 @@ def download_file(url, proxies=None, prefix="grafana-", suffix=".deb"):
>> if chunk:
>> f.write(chunk)
>> return path
>> +
>> +
>> +def parse_snap_
>> + for codename in SNAP_CODENAME_
>> + if channel.
>> + return SNAP_CODENAME_
>> + try:
>> + major_ver = int(channel.
>> + except ValueError:
>> + return -1
>> + return major_ver
>> +
>> +
>> +def check_snap_
>> + """Check if given channel could be configured
>> +
>> + Set unit status accordingly, and return a status code:
>> +
>> + -1: Invalid channel
>> + 0: Channel configurable, needs manual upgrade
>> + 1: Channel unchanged
>> + 2: Would result in downgrade
>> + """
>> + installed_ver = parse_snap_
>> + snap.get_
>
> This causes IndexError if the snap is not installed (because "tracking:" does not exist in the output of "snap info grafana").
> OTOH, SNAP_CODENAME_MAP says "latest" is "7", but "latest/stable" is v6.7.4 (so "6" should be returned).
>
>> + )
>> + new_ver = parse_snap_
>
> if "6/stable" is shared, "6" will be returned.
> if "latest/edge" is shared, 6 would also need to be returned.
> if "7/stable" is shared, "6" will be returned.
Not sure I understand -- this should be True:
parse_
> if "edge" is shared, "6" should be returned (I guess, when no "/" exists, "latest/" should be added as prefix).
>
>> +
>> + if new_ver == -1:
>> + msg = "Invalid snap channel: {}".format(channel)
>> + status_
>> + log(msg)
>> + return -1
>> + if installed_ver == new_ver:
>> + log("Channel version unchanged: {}".format(
>> + return 1
>
> What if a minor version exists? We had to upgrade from v6.7.3 to v6.7.4 because of a security vuln.
>
>> + if installed_ver <= new_ver:
>> + msg = (
>> + "PACKAGE UPGRADE REQUIRES MANUAL INTERVENTION: see {} for "
>> + "more info"
>> + ).format(
>> + status_
>> + log(msg)
>> + return 0
>> + msg = (
>> + "PACKAGE DOWNGRADES ARE NOT SUPPORTED BY THIS CHARM - to clear "
>> + "this message, set config 'snap_channel' to '{}/stable'"
>> + ).format(
>> + status_
>> + log(msg)
>> + return 2
>
>
Alvaro Uria (aluria) : | # |
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.
Paul Goins (vultaire) wrote : | # |
2 comments, one is from our face-to-face discussion and the other is a small thing which caught my eye.
Peter Sabaini (peter-sabaini) wrote : | # |
Hi,
as discussed I'm catching snap_channel=
cheers,
peter.
Paul Goins (vultaire) wrote : | # |
A few quick comments added.
Peter Sabaini (peter-sabaini) wrote : | # |
Thanks Paul. After talking to Jeremy I've also changed the behaviour such that minor refreshes will get auto-installed instead of blocking for manual upgrade
Paul Goins (vultaire) wrote : | # |
I think this looks okay, however I noticed one mismatch re: the default channel. Please review the diff comments.
If this delta was intentional, consider this a +1 from me. Otherwise, please change as necessary.
Peter Sabaini (peter-sabaini) wrote : | # |
On 23.06.20 10:19, Paul Goins wrote:
> I think this looks okay, however I noticed one mismatch re: the default channel. Please review the diff comments.
>
> If this delta was intentional, consider this a +1 from me. Otherwise, please change as necessary.
Yes, the update for the config default is intentional. Rationale is that we used to default to 6/x track as there were some uncertainties around performance, but that has been resolved since.
Thanks for the review!
> Diff comments:
>
>> diff --git a/config.yaml b/config.yaml
>> index 8788be1..69d0116 100644
>> --- a/config.yaml
>> +++ b/config.yaml
>> @@ -187,11 +187,12 @@ options:
>> package will be installed using apt-get.
>> If set to "snap", snap package will be installed
>> snap_channel:
>> - default: "stable"
>> + default: "7/stable"
>
> I just want to check on this - here we're changing the default to 7/stable (instead of implicitly latest/stable), but in the version lookup table, "stable" resolves to 6 rather than 7. Is this intentional?
>
>> type: string
>> description: |
>> - If install_method is set to "snap" this option controlls channel name.
>> - Supported values are: "stable", "candidate", "beta" and "edge"
>> + If install_method is set to "snap" this option controls channel name being used.
>> +
>> + Note: a) changing this option will not automatically upgrade snaps -- see the do-upgrade action for this, b) downgrading snaps is unsupported, and c) using the track symbolic name "latest" is also unsupported (use an explicit track name instead, e.g. "7/stable")
>> external_network:
>> default: "ext_net"
>> type: string
>
>
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change cannot be self approved, setting status to needs review.
Preview Diff
1 | diff --git a/README.md b/README.md |
2 | index bd6e7b8..c9a1323 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -1,8 +1,8 @@ |
6 | -#Overview |
7 | +# Overview |
8 | |
9 | This charm provides the latest stable version of Grafana. |
10 | |
11 | -#Usage |
12 | +# Usage |
13 | |
14 | juju deploy grafana |
15 | juju add-relation prometheus:grafana-source grafana:grafana-source |
16 | @@ -15,7 +15,7 @@ To retrieve autogenerated password run: |
17 | |
18 | juju run-action --wait grafana/0 get-admin-password |
19 | |
20 | -#Actions |
21 | +# Actions |
22 | |
23 | This charm supports importing dashboards, simply run: |
24 | |
25 | @@ -33,7 +33,7 @@ There is also an action to create an API key, run: |
26 | |
27 | where the keyrole is one of Viewer, Editor, Read Only Editor or Admin. |
28 | |
29 | -##User Management Actions |
30 | +## User Management Actions |
31 | |
32 | Currently user management is only implemented to manipulate users in the default |
33 | organisation. |
34 | @@ -69,7 +69,14 @@ To delete a user, you simply run: |
35 | juju run-action --wait grafana/0 delete-user login=john |
36 | |
37 | |
38 | -#Auth proxy |
39 | +## Upgrade action |
40 | + |
41 | +To perform an upgrade after changing `snap_channel` an upgrade action must be run: |
42 | + |
43 | + juju run-action --wait grafana/0 do-upgrade |
44 | + |
45 | + |
46 | +# Auth proxy |
47 | |
48 | If deployed behind a reverse proxy, you can configure Grafana to let |
49 | it handle authentication by enabled auth-proxy. |
50 | @@ -92,7 +99,7 @@ and `anonymous=true` is set, those paths will be accessible (view only) to |
51 | non-authenticated users. |
52 | |
53 | |
54 | -#Development |
55 | +# Development |
56 | |
57 | Explicitly set `JUJU_REPOSITORY`: |
58 | |
59 | @@ -109,7 +116,7 @@ Assemble the charm: |
60 | |
61 | charm build |
62 | |
63 | -#Contact Information |
64 | +# Contact Information |
65 | |
66 | Author: Alvaro Uria <alvaro.uria@canonical.com>, Jacek Nykis <jacek.nykis@canonical.com> |
67 | Report bugs at: https://bugs.launchpad.net/grafana-charm |
68 | diff --git a/actions.yaml b/actions.yaml |
69 | index b90282b..71de777 100644 |
70 | --- a/actions.yaml |
71 | +++ b/actions.yaml |
72 | @@ -77,3 +77,6 @@ change-user-role: |
73 | description: New role to use, one of Viewer, Editor, Read Only Editor or Admin |
74 | required: [login, new-role] |
75 | additionalProperties: false |
76 | +do-upgrade: |
77 | + description: Perform an upgrade |
78 | + |
79 | diff --git a/actions/do-upgrade b/actions/do-upgrade |
80 | new file mode 100755 |
81 | index 0000000..edb0eb9 |
82 | --- /dev/null |
83 | +++ b/actions/do-upgrade |
84 | @@ -0,0 +1,63 @@ |
85 | +#!/usr/local/sbin/charm-env python3 |
86 | +# Performs an upgrade |
87 | + |
88 | +import sys |
89 | + |
90 | +from charmhelpers import fetch |
91 | +from charmhelpers.core.hookenv import ( |
92 | + config, |
93 | + function_fail, |
94 | + function_set, |
95 | + status_set, |
96 | +) |
97 | +from charms.layer import snap |
98 | +from charms.reactive import remove_state |
99 | + |
100 | + |
101 | +sys.path.append("lib") |
102 | + |
103 | +from charms.layer.grafana import ChangeStatus, check_snap_channel |
104 | + |
105 | + |
106 | +SNAP_NAME = "grafana" |
107 | +PACKAGES = ["grafana"] |
108 | + |
109 | + |
110 | +def snap_upgrade(channel): |
111 | + if not snap.is_installed(SNAP_NAME): |
112 | + # Only upgrade if snap is in fact installed |
113 | + function_fail("Snap not installed, can't upgrade") |
114 | + return |
115 | + result = check_snap_channel(SNAP_NAME, channel) |
116 | + if result == ChangeStatus.UPGRADE: |
117 | + snap.install( |
118 | + SNAP_NAME, channel=channel, force_dangerous=False, |
119 | + ) |
120 | + function_set({"result": "Upgraded, channel {}".format(channel)}) |
121 | + status_set("active", "Ready") |
122 | + remove_state("grafana.change-block") |
123 | + elif result == ChangeStatus.UNCHANGED: |
124 | + function_fail("Nothing to upgrade") |
125 | + elif result == ChangeStatus.DOWNGRADE: |
126 | + function_fail("Abort: would result in downgrade") |
127 | + elif result == ChangeStatus.INVALID: |
128 | + function_fail("Invalid channel {}".format(channel)) |
129 | + elif result == ChangeStatus.LATEST: |
130 | + function_fail('"latest" track unsuported, revert channel config') |
131 | + |
132 | + |
133 | +def apt_upgrade(): |
134 | + fetch.apt_install(PACKAGES, fatal=True) |
135 | + function_set({"result": "Upgraded package"}) |
136 | + |
137 | + |
138 | +def main(): |
139 | + cfg = config() |
140 | + if cfg["install_method"] == "snap": |
141 | + snap_upgrade(cfg["snap_channel"]) |
142 | + else: # apt method |
143 | + apt_upgrade() |
144 | + |
145 | + |
146 | +if __name__ == "__main__": |
147 | + main() |
148 | diff --git a/config.yaml b/config.yaml |
149 | index 8788be1..69d0116 100644 |
150 | --- a/config.yaml |
151 | +++ b/config.yaml |
152 | @@ -187,11 +187,12 @@ options: |
153 | package will be installed using apt-get. |
154 | If set to "snap", snap package will be installed |
155 | snap_channel: |
156 | - default: "stable" |
157 | + default: "7/stable" |
158 | type: string |
159 | description: | |
160 | - If install_method is set to "snap" this option controlls channel name. |
161 | - Supported values are: "stable", "candidate", "beta" and "edge" |
162 | + If install_method is set to "snap" this option controls channel name being used. |
163 | + |
164 | + Note: a) changing this option will not automatically upgrade snaps -- see the do-upgrade action for this, b) downgrading snaps is unsupported, and c) using the track symbolic name "latest" is also unsupported (use an explicit track name instead, e.g. "7/stable") |
165 | external_network: |
166 | default: "ext_net" |
167 | type: string |
168 | diff --git a/lib/charms/layer/grafana.py b/lib/charms/layer/grafana.py |
169 | index 1ddd431..9853dc2 100644 |
170 | --- a/lib/charms/layer/grafana.py |
171 | +++ b/lib/charms/layer/grafana.py |
172 | @@ -1,5 +1,5 @@ |
173 | #!/usr/bin/python3 |
174 | - |
175 | +import enum |
176 | import json |
177 | import tempfile |
178 | import subprocess |
179 | @@ -10,7 +10,24 @@ from charmhelpers.core import unitdata |
180 | from charmhelpers.core.hookenv import ( |
181 | config, |
182 | log, |
183 | + status_set, |
184 | ) |
185 | +from charms.layer import snap |
186 | + |
187 | + |
188 | +class ChangeStatus(enum.Enum): |
189 | + LATEST = -2 |
190 | + INVALID = -1 |
191 | + UPGRADE = 0 |
192 | + UNCHANGED = 1 |
193 | + DOWNGRADE = 2 |
194 | + |
195 | + |
196 | +VERSION_CHANGE_URL = "<url>" |
197 | +SNAP_CODENAME_MAP = { |
198 | + "latest": -2, # special case: we do not allow changes to the latest track |
199 | + "stable": 6, |
200 | +} |
201 | |
202 | |
203 | def get_admin_password(): |
204 | @@ -90,3 +107,72 @@ def download_file(url, proxies=None, prefix="grafana-", suffix=".deb"): |
205 | if chunk: |
206 | f.write(chunk) |
207 | return path |
208 | + |
209 | + |
210 | +def parse_snap_major_version(channel): |
211 | + for codename in SNAP_CODENAME_MAP.keys(): |
212 | + if channel.startswith(codename): |
213 | + return SNAP_CODENAME_MAP[codename] |
214 | + try: |
215 | + major_ver = int(channel.partition("/")[0]) |
216 | + except ValueError: |
217 | + return -1 |
218 | + return major_ver |
219 | + |
220 | + |
221 | +def check_snap_channel(snap_name, channel): |
222 | + """Check if given channel could be configured |
223 | + |
224 | + Set unit status accordingly, and return a status code from |
225 | + the ChangeStatus enum: |
226 | + |
227 | + LATEST: Specified "latest" track which we prohibit |
228 | + INVALID: Invalid channel |
229 | + UPGRADE: Channel configurable, needs manual upgrade |
230 | + UNCHANGED: Channel unchanged |
231 | + DOWNGRADE: Would result in downgrade of major version |
232 | + """ |
233 | + installed_ver = parse_snap_major_version( |
234 | + snap.get_installed_channel(snap_name) |
235 | + ) |
236 | + new_ver = parse_snap_major_version(channel) |
237 | + is_refresh_available = snap.is_refresh_available(snap_name) |
238 | + if new_ver == -2: |
239 | + msg = ( |
240 | + 'The "latest" track is not supported; revert the channel config ' |
241 | + "change to unblock" |
242 | + ) |
243 | + status_set("blocked", msg) |
244 | + log(msg) |
245 | + return ChangeStatus.LATEST |
246 | + if new_ver == -1: |
247 | + msg = "Invalid snap channel: {}".format(channel) |
248 | + status_set("blocked", msg) |
249 | + log(msg) |
250 | + return ChangeStatus.INVALID |
251 | + if installed_ver == new_ver and not is_refresh_available: |
252 | + status_set("active", "Ready") |
253 | + log("Channel version unchanged: {}".format(installed_ver)) |
254 | + return ChangeStatus.UNCHANGED |
255 | + if installed_ver == new_ver and is_refresh_available: |
256 | + msg = "Snap is being refreshed" |
257 | + status_set("maintenance", msg) |
258 | + log(msg) |
259 | + snap.refresh(snap_name) |
260 | + status_set("active", "Refresh complete") |
261 | + return ChangeStatus.UNCHANGED |
262 | + if installed_ver < new_ver: |
263 | + msg = ( |
264 | + "PACKAGE UPGRADE REQUIRES MANUAL INTERVENTION: see {} for " |
265 | + "more info" |
266 | + ).format(VERSION_CHANGE_URL) |
267 | + status_set("blocked", msg) |
268 | + log(msg) |
269 | + return ChangeStatus.UPGRADE |
270 | + msg = ( |
271 | + "PACKAGE DOWNGRADES ARE NOT SUPPORTED BY THIS CHARM - to clear " |
272 | + "this message, set config 'snap_channel' to '{}/stable'" |
273 | + ).format(installed_ver) |
274 | + status_set("blocked", msg) |
275 | + log(msg) |
276 | + return ChangeStatus.DOWNGRADE |
277 | diff --git a/reactive/grafana.py b/reactive/grafana.py |
278 | index d12a2c6..c64743a 100644 |
279 | --- a/reactive/grafana.py |
280 | +++ b/reactive/grafana.py |
281 | @@ -18,6 +18,8 @@ from charmhelpers.core import ( |
282 | from charmhelpers.core.templating import render |
283 | from charms.layer import snap |
284 | from charms.layer.grafana import ( |
285 | + ChangeStatus, |
286 | + check_snap_channel, |
287 | download_file, |
288 | get_admin_password, |
289 | get_deb_package_version, |
290 | @@ -171,6 +173,7 @@ def data_path(): |
291 | |
292 | @when("grafana.installed") |
293 | @when("config.changed.install_plugins") |
294 | +@when_not("grafana.change-block") |
295 | def install_plugins(): |
296 | data_dir = data_path() |
297 | if not data_dir: |
298 | @@ -210,6 +213,7 @@ def install_plugins(): |
299 | |
300 | |
301 | @hook("upgrade-charm") |
302 | +@when_not("grafana.change-block") |
303 | def upgrade_charm(): |
304 | hookenv.status_set( |
305 | "maintenance", |
306 | @@ -222,17 +226,7 @@ def upgrade_charm(): |
307 | |
308 | @hook("config-changed") |
309 | def config_changed(): |
310 | - remove_state("grafana.configured") |
311 | config = hookenv.config() |
312 | - if config.changed("dashboards_backup_schedule") or config.changed( |
313 | - "dashboards_backup_dir" |
314 | - ): |
315 | - remove_state("grafana.backup.configured") |
316 | - if config.changed("admin_password") or config.changed("nagios_context"): |
317 | - remove_state("grafana.admin_password.set") |
318 | - if config.changed("install_file") and config.get("install_file"): |
319 | - remove_state("grafana.installed") |
320 | - |
321 | if ( |
322 | config.changed("snap_channel") |
323 | and config["snap_channel"] |
324 | @@ -241,14 +235,31 @@ def config_changed(): |
325 | ): |
326 | # NOTE(aluria): install_method change (apt -> snap, and viceversa) |
327 | # is not supported. However, snap channel change support was missing |
328 | - snap.install( |
329 | - SNAP_NAME, channel=config["snap_channel"], force_dangerous=False, |
330 | - ) |
331 | - hookenv.log( |
332 | - "{} snap channel updated to {}".format( |
333 | - SNAP_NAME, config["snap_channel"] |
334 | + if snap.is_installed(SNAP_NAME): |
335 | + # Check if we should upgrade |
336 | + status = check_snap_channel(SNAP_NAME, config["snap_channel"]) |
337 | + if status == ChangeStatus.UNCHANGED: |
338 | + remove_state("grafana.change-block") |
339 | + else: |
340 | + set_state("grafana.change-block") |
341 | + # We don't need to re-setup so bail out |
342 | + return |
343 | + else: |
344 | + # Install from requested channel |
345 | + snap.install( |
346 | + SNAP_NAME, |
347 | + channel=config["snap_channel"], |
348 | + force_dangerous=False, |
349 | ) |
350 | - ) |
351 | + remove_state("grafana.configured") |
352 | + if config.changed("dashboards_backup_schedule") or config.changed( |
353 | + "dashboards_backup_dir" |
354 | + ): |
355 | + remove_state("grafana.backup.configured") |
356 | + if config.changed("admin_password") or config.changed("nagios_context"): |
357 | + remove_state("grafana.admin_password.set") |
358 | + if config.changed("install_file") and config.get("install_file"): |
359 | + remove_state("grafana.installed") |
360 | |
361 | |
362 | def check_ports(new_port): |
363 | @@ -327,6 +338,7 @@ def add_backup_api_keys(): |
364 | |
365 | @when("grafana.installed") |
366 | @when_not("grafana.configured") |
367 | +@when_not("grafana.change-block") |
368 | def setup_grafana(): |
369 | kv = unitdata.kv() |
370 | source = kv.get("install_method") |
371 | @@ -358,6 +370,7 @@ def setup_grafana(): |
372 | |
373 | @when("grafana.started") |
374 | @when_not("grafana.backup.configured") |
375 | +@when_not("grafana.change-block") |
376 | def setup_backup_shedule(): |
377 | # copy script, create cronjob, ensure directory exists |
378 | |
379 | @@ -395,6 +408,7 @@ def setup_backup_shedule(): |
380 | |
381 | @when("grafana.configured") |
382 | @when_not("grafana.started") |
383 | +@when_not("grafana.change-block") |
384 | def restart_grafana(): |
385 | kv = unitdata.kv() |
386 | source = kv.get("install_method") |
387 | @@ -424,6 +438,7 @@ def restart_grafana(): |
388 | |
389 | @when("grafana.started") |
390 | @when("nrpe-external-master.available") |
391 | +@when_not("grafana.change-block") |
392 | def update_nrpe_config(svc): |
393 | kv = unitdata.kv() |
394 | source = kv.get("install_method") |
395 | @@ -458,6 +473,7 @@ def wipe_nrpe_checks(): |
396 | @when("grafana.started") |
397 | @when("grafana-source.available") |
398 | @when("grafana.admin_password.set") |
399 | +@when_not("grafana.change-block") |
400 | def configure_sources(relation): |
401 | sources = relation.datasources() |
402 | gf_adminpasswd = get_admin_password() |
403 | @@ -908,6 +924,7 @@ def get_main_org_id(): |
404 | |
405 | @when("grafana.started") |
406 | @when_not("grafana.admin_password.set") |
407 | +@when_not("grafana.change-block") |
408 | def check_adminuser(): |
409 | """Create Adminuser if not existing. |
410 | |
411 | @@ -990,6 +1007,7 @@ def hpwgen(passwd, salt): |
412 | |
413 | |
414 | @when("grafana.started", "endpoint.dashboards.requests") |
415 | +@when_not("grafana.change-block") |
416 | def import_dashboards(dashboards): |
417 | for request in dashboards.new_requests: |
418 | try: |
419 | diff --git a/tests/functional/tests/bundles/bionic-snap.yaml b/tests/functional/tests/bundles/bionic-snap.yaml |
420 | index f34416b..a14fd81 100644 |
421 | --- a/tests/functional/tests/bundles/bionic-snap.yaml |
422 | +++ b/tests/functional/tests/bundles/bionic-snap.yaml |
423 | @@ -4,6 +4,7 @@ applications: |
424 | num_units: 1 |
425 | options: |
426 | install_method: snap |
427 | + snap_channel: 6/stable |
428 | ceph-mon: |
429 | charm: cs:ceph-mon |
430 | num_units: 1 |
431 | diff --git a/tests/functional/tests/bundles/focal-snap.yaml b/tests/functional/tests/bundles/focal-snap.yaml |
432 | index d824b05..79ac514 100644 |
433 | --- a/tests/functional/tests/bundles/focal-snap.yaml |
434 | +++ b/tests/functional/tests/bundles/focal-snap.yaml |
435 | @@ -4,6 +4,7 @@ applications: |
436 | num_units: 1 |
437 | options: |
438 | install_method: snap |
439 | + snap_channel: 6/stable |
440 | ceph-mon: |
441 | charm: cs:ceph-mon |
442 | num_units: 1 |
443 | diff --git a/tests/functional/tests/test_grafana.py b/tests/functional/tests/test_grafana.py |
444 | index c9bbd72..5f452d9 100644 |
445 | --- a/tests/functional/tests/test_grafana.py |
446 | +++ b/tests/functional/tests/test_grafana.py |
447 | @@ -30,6 +30,10 @@ class BaseGrafanaTest(unittest.TestCase): |
448 | ) |
449 | cls.grafana_ip = model.get_app_ips(cls.application_name)[0] |
450 | |
451 | + def get_unit_status(self, unit): |
452 | + u = model.get_unit_from_name(unit) |
453 | + return u.workload_status_message |
454 | + |
455 | |
456 | class CharmOperationTest(BaseGrafanaTest): |
457 | """Verify operations.""" |
458 | @@ -93,7 +97,7 @@ class CharmOperationTest(BaseGrafanaTest): |
459 | content = result.get("Stdout") |
460 | self.assertTrue(expected_nrpe_check in content) |
461 | |
462 | - def test_grafana_imported_dashboards(self): |
463 | + def test_10_grafana_imported_dashboards(self): |
464 | """Check that Grafana dashboards expected are there""" |
465 | action = model.run_action( |
466 | self.lead_unit_name, "get-admin-password", raise_on_failure=True, |
467 | @@ -123,7 +127,7 @@ class CharmOperationTest(BaseGrafanaTest): |
468 | ) |
469 | ) |
470 | |
471 | - def test_grafana_create_user(self): |
472 | + def test_12_grafana_create_user(self): |
473 | """Run the create-user action.""" |
474 | params = { |
475 | "name": "FooUser", |
476 | @@ -145,3 +149,66 @@ class CharmOperationTest(BaseGrafanaTest): |
477 | ) |
478 | self.assertTrue(req.status_code == 200) |
479 | self.assertTrue("name" in req.json()) |
480 | + |
481 | + |
482 | +class SnappedGrafanaTest(BaseGrafanaTest): |
483 | + def test_50_snap_upgrade(self): |
484 | + """Test a snap upgrade from 6 to 7""" |
485 | + model.set_application_config( |
486 | + self.application_name, {"snap_channel": "7/stable"} |
487 | + ) |
488 | + model.block_until_unit_wl_status(self.lead_unit_name, "blocked") |
489 | + model.block_until_all_units_idle() |
490 | + status_message = self.get_unit_status(self.lead_unit_name) |
491 | + self.assertTrue( |
492 | + "PACKAGE UPGRADE REQUIRES MANUAL INTERVENTION" in status_message |
493 | + ) |
494 | + action = model.run_action(self.lead_unit_name, "do-upgrade") |
495 | + self.assertEqual(action.data["status"], "completed") |
496 | + self.assertTrue("Upgraded" in action.data["results"]["result"]) |
497 | + |
498 | + def test_51_snap_downgrade_block(self): |
499 | + """Test that after the previous upgrade, downgrades are blocked""" |
500 | + model.set_application_config( |
501 | + self.application_name, {"snap_channel": "6/stable"} |
502 | + ) |
503 | + model.block_until_unit_wl_status(self.lead_unit_name, "blocked") |
504 | + model.block_until_all_units_idle() |
505 | + status_message = self.get_unit_status(self.lead_unit_name) |
506 | + self.assertTrue( |
507 | + "PACKAGE DOWNGRADES ARE NOT SUPPORTED" in status_message |
508 | + ) |
509 | + model.set_application_config( # unblock unit again |
510 | + self.application_name, {"snap_channel": "7/stable"} |
511 | + ) |
512 | + model.block_until_all_units_idle() |
513 | + downgrade_cmd = "sudo snap refresh grafana --channel=6/stable" |
514 | + model.run_on_unit(self.lead_unit_name, downgrade_cmd) |
515 | + model.set_application_config( # set it back to 6 |
516 | + self.application_name, {"snap_channel": "6/stable"} |
517 | + ) |
518 | + model.block_until_all_units_idle() |
519 | + |
520 | + def test_52_latest(self): |
521 | + """Test: track 'latest' is blocked""" |
522 | + model.set_application_config( |
523 | + self.application_name, {"snap_channel": "latest/stable"} |
524 | + ) |
525 | + model.block_until_unit_wl_status(self.lead_unit_name, "blocked") |
526 | + model.block_until_all_units_idle() |
527 | + status_message = self.get_unit_status(self.lead_unit_name) |
528 | + self.assertTrue( |
529 | + 'The "latest" track is not supported; revert ' in status_message |
530 | + ) |
531 | + model.set_application_config( # unblock unit again |
532 | + self.application_name, {"snap_channel": "6/stable"} |
533 | + ) |
534 | + model.block_until_all_units_idle() |
535 | + |
536 | + |
537 | +class AptGrafanaTest(BaseGrafanaTest): |
538 | + def test_50_apt_upgrade(self): |
539 | + """Test an apt upgrade""" |
540 | + action = model.run_action(self.lead_unit_name, "do-upgrade") |
541 | + self.assertEqual(action.data["status"], "completed") |
542 | + self.assertTrue("Upgraded" in action.data["results"]["result"]) |
543 | diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml |
544 | index c7da132..56abe83 100644 |
545 | --- a/tests/functional/tests/tests.yaml |
546 | +++ b/tests/functional/tests/tests.yaml |
547 | @@ -1,16 +1,21 @@ |
548 | charm_name: grafana |
549 | gate_bundles: |
550 | - - focal |
551 | - - bionic |
552 | - - xenial |
553 | - - bionic-snap |
554 | - - focal-snap |
555 | + - model_apt_install: focal |
556 | + - model_apt_install: bionic |
557 | + - model_apt_install: xenial |
558 | + - model_snap_install: bionic-snap |
559 | + - model_snap_install: focal-snap |
560 | smoke_bundles: |
561 | - - bionic |
562 | + - model_snap_install: bionic-snap |
563 | dev_bundles: |
564 | - - bionic |
565 | + - model_snap_install: bionic-snap |
566 | tests: |
567 | - - tests.test_grafana.CharmOperationTest |
568 | + - model_snap_install: |
569 | + - tests.test_grafana.CharmOperationTest |
570 | + - tests.test_grafana.SnappedGrafanaTest |
571 | + - model_apt_install: |
572 | + - tests.test_grafana.CharmOperationTest |
573 | + - tests.test_grafana.AptGrafanaTest |
574 | target_deploy_status: |
575 | grafana: |
576 | workload-status-message: Started |
577 | diff --git a/tests/unit/test_grafana.py b/tests/unit/test_grafana.py |
578 | index 0b8e749..57e730d 100644 |
579 | --- a/tests/unit/test_grafana.py |
580 | +++ b/tests/unit/test_grafana.py |
581 | @@ -1,7 +1,13 @@ |
582 | from os.path import isfile |
583 | +from unittest import mock |
584 | +import sys |
585 | import unittest |
586 | |
587 | -from charms.layer.grafana import ( |
588 | + |
589 | +sys.modules["charms.layer.snap"] = mock.Mock() |
590 | + |
591 | + |
592 | +from charms.layer.grafana import ( # noqa E402 |
593 | download_file, |
594 | get_cmd_output, |
595 | get_deb_package_version, |
596 | diff --git a/tox.ini b/tox.ini |
597 | index b5cb33b..486c489 100644 |
598 | --- a/tox.ini |
599 | +++ b/tox.ini |
600 | @@ -1,6 +1,6 @@ |
601 | [tox] |
602 | skipsdist=True |
603 | -envlist = unit, functional |
604 | +envlist = unit, functional, func-smoke |
605 | skip_missing_interpreters = True |
606 | |
607 | [testenv] |
608 | @@ -31,6 +31,12 @@ changedir = {toxinidir}/tests/functional |
609 | commands = functest-run-suite --keep-model |
610 | deps = -r{toxinidir}/tests/functional/requirements.txt |
611 | |
612 | +[testenv:func-smoke] |
613 | +changedir = {toxinidir}/tests/functional |
614 | +commands = |
615 | + functest-run-suite --keep-model --smoke |
616 | +deps = -r{toxinidir}/tests/functional/requirements.txt |
617 | + |
618 | [testenv:lint] |
619 | commands = |
620 | black --check --line-length 79 reactive lib tests |
I've added a few comments inline