Merge ~cjwatson/launchpad:charm-appserver-rolling-restart into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 54cf70650c8acf0b98083a95ea597754688f6a0a
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:charm-appserver-rolling-restart
Merge into: launchpad:master
Diff against target: 144 lines (+31/-16)
5 files modified
charm/launchpad-appserver/charmcraft.yaml (+13/-0)
charm/launchpad-appserver/layer.yaml (+1/-0)
charm/launchpad-appserver/reactive/launchpad-appserver.py (+14/-16)
charm/launchpad-appserver/templates/gunicorn.conf.py.j2 (+2/-0)
charm/launchpad-appserver/templates/launchpad.service.j2 (+1/-0)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+440349@code.launchpad.net

Commit message

charm: Use rolling restart for launchpad-appserver

Description of the change

As long as we have more than one appserver unit, this ensures that the application remains responsive during upgrades, using the coordinator layer to deal with communication between units.

With gunicorn >= 20.0, waiting for the application to be responsive on each unit turns out to be a matter of setting `preload_app = True` (which causes the application to be loaded before the arbiter signals readiness, and also saves a fair amount of memory) and using `Type=notify` in the systemd service. I dropped the old soft-restart mechanism when only `*-lazr.conf` files are changed, since it doesn't quite work with application preloading and it wasn't all that much of a win anyway.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/charm/launchpad-appserver/charmcraft.yaml b/charm/launchpad-appserver/charmcraft.yaml
2index b35779b..613dc0b 100644
3--- a/charm/launchpad-appserver/charmcraft.yaml
4+++ b/charm/launchpad-appserver/charmcraft.yaml
5@@ -45,10 +45,23 @@ parts:
6 - layers
7 prime:
8 - "-layers"
9+ layer-coordinator:
10+ source: https://git.launchpad.net/layer-coordinator
11+ source-commit: "fa27fc93e0b08000963e83a6bfe49812d890dfcf"
12+ source-submodules: []
13+ source-type: git
14+ plugin: dump
15+ organize:
16+ "*": layers/layer/coordinator/
17+ stage:
18+ - layers
19+ prime:
20+ - "-layers"
21 launchpad-appserver:
22 after:
23 - charm-wheels
24 - launchpad-layers
25+ - layer-coordinator
26 source: .
27 plugin: reactive
28 build-snaps: [charm/2.x/stable]
29diff --git a/charm/launchpad-appserver/layer.yaml b/charm/launchpad-appserver/layer.yaml
30index dae6f8a..87a5643 100644
31--- a/charm/launchpad-appserver/layer.yaml
32+++ b/charm/launchpad-appserver/layer.yaml
33@@ -1,5 +1,6 @@
34 includes:
35 - layer:launchpad-base
36+ - layer:coordinator
37 - interface:memcache
38 repo: https://git.launchpad.net/launchpad
39 options:
40diff --git a/charm/launchpad-appserver/reactive/launchpad-appserver.py b/charm/launchpad-appserver/reactive/launchpad-appserver.py
41index 99ef7f6..1c32282 100644
42--- a/charm/launchpad-appserver/reactive/launchpad-appserver.py
43+++ b/charm/launchpad-appserver/reactive/launchpad-appserver.py
44@@ -6,6 +6,7 @@ import subprocess
45 from multiprocessing import cpu_count
46
47 from charmhelpers.core import hookenv, host, templating
48+from charms.coordinator import acquire
49 from charms.launchpad.base import (
50 config_file_path,
51 configure_cron,
52@@ -24,6 +25,7 @@ from charms.reactive import (
53 set_flag,
54 set_state,
55 when,
56+ when_none,
57 when_not,
58 )
59 from ols import base, postgres
60@@ -64,6 +66,7 @@ def configure_gunicorn(config):
61 templating.render(
62 "launchpad.service.j2", "/lib/systemd/system/launchpad.service", config
63 )
64+ subprocess.run(["systemctl", "daemon-reload"], check=True)
65 host.add_user_to_group("syslog", base.user())
66 templating.render("rsyslog.j2", "/etc/rsyslog.d/22-launchpad.conf", config)
67
68@@ -78,13 +81,6 @@ def configure_logrotate(config):
69 )
70
71
72-def restart(soft=False):
73- if soft:
74- reload_or_restart("launchpad")
75- else:
76- host.service_restart("launchpad")
77-
78-
79 def config_files():
80 files = []
81 files.extend(lazr_config_files())
82@@ -100,7 +96,7 @@ def config_files():
83 "session-db.master.available",
84 "memcache.available",
85 )
86-@when_not("service.configured")
87+@when_none("coordinator.requested.restart", "service.configured")
88 def configure():
89 session_db = endpoint_from_flag("session-db.master.available")
90 memcache = endpoint_from_flag("memcache.available")
91@@ -133,19 +129,21 @@ def configure():
92 configure_logrotate(config)
93 configure_cron(config, "crontab.j2")
94
95- restart_type = None
96 if helpers.any_file_changed(
97 [base.version_info_path(), "/lib/systemd/system/launchpad.service"]
98+ + config_files()
99 ):
100- restart_type = "hard"
101- elif helpers.any_file_changed(config_files()):
102- restart_type = "soft"
103- if restart_type is None:
104- hookenv.log("Not restarting, since no config files were changed")
105+ hookenv.log("Config files changed; waiting for restart lock")
106+ acquire("restart")
107 else:
108- hookenv.log(f"Config files changed; performing {restart_type} restart")
109- restart(soft=(restart_type == "soft"))
110+ hookenv.log("Not restarting, since no config files were changed")
111+ set_state("service.configured")
112+
113
114+@when("coordinator.granted.restart")
115+def restart():
116+ hookenv.log("Restarting application server")
117+ host.service_restart("launchpad")
118 set_state("service.configured")
119
120
121diff --git a/charm/launchpad-appserver/templates/gunicorn.conf.py.j2 b/charm/launchpad-appserver/templates/gunicorn.conf.py.j2
122index efd8a1e..09d8866 100644
123--- a/charm/launchpad-appserver/templates/gunicorn.conf.py.j2
124+++ b/charm/launchpad-appserver/templates/gunicorn.conf.py.j2
125@@ -1,5 +1,7 @@
126 bind = [":{{ port_main }}", ":{{ port_xmlrpc }}"]
127 workers = {{ wsgi_workers }}
128+# Incompatible with `reload = True`, but that's fine for a Juju deployment.
129+preload_app = True
130 # This is set relatively low to work around memory leaks on Python 3.
131 max_requests = 2000
132 log_level = "DEBUG"
133diff --git a/charm/launchpad-appserver/templates/launchpad.service.j2 b/charm/launchpad-appserver/templates/launchpad.service.j2
134index fb5c372..c9290f4 100644
135--- a/charm/launchpad-appserver/templates/launchpad.service.j2
136+++ b/charm/launchpad-appserver/templates/launchpad.service.j2
137@@ -4,6 +4,7 @@ After=network.target
138 ConditionPathExists=!{{ code_dir }}/maintenance.txt
139
140 [Service]
141+Type=notify
142 User=launchpad
143 Group=launchpad
144 WorkingDirectory={{ code_dir }}

Subscribers

People subscribed via source and target branches

to status/vote changes: