Merge ~peter-sabaini/charm-grafana:implement-manual-upgrade into charm-grafana:master

Proposed by Peter Sabaini
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)
Reviewer Review Type Date Requested Status
Paul Goins Needs Fixing
Review via email: mp+385281@code.launchpad.net

Commit message

Upgrade support

To post a comment you must log in.
Revision history for this message
Alvaro Uria (aluria) wrote :

I've added a few comments inline

Revision history for this message
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.check_output(["snap", "info", "grafana"]).decode("utf-8", errors="replace").partition("channels:")[-1].splitlines() if i.find("/") != -1]])
{'latest/stable:': '6.7.4', 'latest/candidate:': '6.7.4', 'latest/beta:': '6.7.4', 'latest/edge:': '6.7.4', '7/stable:': '7.0.3', '7/candidate:': '7.0.3', '7/beta:': '7.0.3', '7/edge:': '7.0.3', '6/stable:': '6.7.4', '6/candidate:': '6.7.4', '6/beta:': '6.7.4', '6/edge:': '6.7.4', '5/stable:': '5.0.0', '5/candidate:': '^', '5/beta:': '^', '5/edge:': '^'}

Revision history for this message
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/layer/grafana.py b/lib/charms/layer/grafana.py
>> index 1ddd431..bcc7977 100644
>> --- a/lib/charms/layer/grafana.py
>> +++ b/lib/charms/layer/grafana.py
>> @@ -90,3 +99,54 @@ def download_file(url, proxies=None, prefix="grafana-", suffix=".deb"):
>> if chunk:
>> f.write(chunk)
>> return path
>> +
>> +
>> +def parse_snap_major_version(channel):
>> + for codename in SNAP_CODENAME_MAP.keys():
>> + if channel.startswith(codename):
>> + return SNAP_CODENAME_MAP[codename]
>> + try:
>> + major_ver = int(channel.partition("/")[0])
>> + except ValueError:
>> + return -1
>> + return major_ver
>> +
>> +
>> +def check_snap_channel(snap_name, channel):
>> + """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_major_version(
>> + snap.get_installed_channel(snap_name)
>
> 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_major_version(channel)
>
> 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_snap_major_version("7/stable") == 7

> 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_set("blocked", msg)
>> + log(msg)
>> + return -1
>> + if installed_ver == new_ver:
>> + log("Channel version unchanged: {}".format(installed_ver))
>> + 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(VERSION_CHANGE_URL)
>> + status_set("blocked", msg)
>> + log(msg)
>> + return 0
>> + msg = (
>> + "PACKAGE DOWNGRADES ARE NOT SUPPORTED BY THIS CHARM - to clear "
>> + "this message, set config 'snap_channel' to '{}/stable'"
>> + ).format(installed_ver)
>> + status_set("blocked", msg)
>> + log(msg)
>> + return 2
>
>

Revision history for this message
Alvaro Uria (aluria) :
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
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.

review: Needs Fixing
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Hi,

as discussed I'm catching snap_channel="latest.*" now, and blocking changes in config or relation when the charm got blocked due to channel updates.

cheers,
peter.

Revision history for this message
Paul Goins (vultaire) wrote :

A few quick comments added.

Revision history for this message
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

Revision history for this message
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.

Revision history for this message
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
>
>

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change cannot be self approved, setting status to needs review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/README.md b/README.md
2index 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
68diff --git a/actions.yaml b/actions.yaml
69index 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+
79diff --git a/actions/do-upgrade b/actions/do-upgrade
80new file mode 100755
81index 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()
148diff --git a/config.yaml b/config.yaml
149index 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
168diff --git a/lib/charms/layer/grafana.py b/lib/charms/layer/grafana.py
169index 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
277diff --git a/reactive/grafana.py b/reactive/grafana.py
278index 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:
419diff --git a/tests/functional/tests/bundles/bionic-snap.yaml b/tests/functional/tests/bundles/bionic-snap.yaml
420index 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
431diff --git a/tests/functional/tests/bundles/focal-snap.yaml b/tests/functional/tests/bundles/focal-snap.yaml
432index 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
443diff --git a/tests/functional/tests/test_grafana.py b/tests/functional/tests/test_grafana.py
444index 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"])
543diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml
544index 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
577diff --git a/tests/unit/test_grafana.py b/tests/unit/test_grafana.py
578index 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,
596diff --git a/tox.ini b/tox.ini
597index 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

Subscribers

People subscribed via source and target branches

to all changes: