Merge ~wck0/landscape-charm:lndeng-539-hashid-leader-only into landscape-charm:main

Proposed by Bill Kronholm
Status: Merged
Merge reported by: Bill Kronholm
Merged at revision: bf0ced80654dab4d270e73fc8970cab08472374a
Proposed branch: ~wck0/landscape-charm:lndeng-539-hashid-leader-only
Merge into: landscape-charm:main
Diff against target: 888 lines (+301/-217)
3 files modified
README.md (+2/-3)
src/charm.py (+293/-207)
src/haproxy-config.yaml (+6/-7)
Reviewer Review Type Date Requested Status
Kevin Nasto Approve
Review via email: mp+445573@code.launchpad.net

Commit message

Only the leader unit should serve hash-id-databases.

Fix bug where package-upload backend might be lost

To post a comment you must log in.
Revision history for this message
Bill Kronholm (wck0) wrote :

Manual Testing Instructions:

0. Grab the new code.

1. From the repo directory, build the project:
make build

2. Once you are able, add some new landscape-server units:
juju add-unit landscape-server -n 4 # or however many extras you want; add at least 2 more units

3. Once you can, monitor the haproxy log file on the haproxy/0 unit for http requests to the /upload and /hash-id-databases endpoints. Do this however you like, but here is one way:
juju ssh haproxy/0
tail -f /var/log/haproxy.log | grep -e database -e upload

4. Meanwhile, wait for the landscape-server units to settle. Once they do, identify the landscape-server leader and the IP address for haproxy:
watch --color juju status --color --relations

5. Make repeated requests to the /upload and /hash-id-databases endpoints. Observe that only the leader unit responds to these requests:
curl --insecure https://IP.ADDR.OF.HAPROXY/upload
curl --insecure https://IP.ADDR.OF.HAPROXY/hash-id-databases

6. Make repeated requests to the / endpoint (or any other). Observe that haproxy balances the load across all landscape-server units.
curl --insecure https://IP.ADDR.OF.HAPROXY/

7. Remove the landscape-server leader unit:
juju remove-unit landscape-server/n # replace n with the leader's number

8. Once the new leader is elected, repeat 5 and 6 above, observing the requests in step 5 go only to the new leader. If the leader has not yet settled, these requests should yield 503 NOSRV from haproxy.

9. Repeat steps 7 and 8 ad nauseam.

10. Clean up:
make clean

0f290f1... by Bill Kronholm

Remove logging warnings. Revert changes to postgres charm.

Revision history for this message
Bill Kronholm (wck0) wrote :

Updated

Revision history for this message
Kevin Nasto (silverdrake11) wrote :

Did you run black on the code? It's confusing to see the changes you made vs. formatting changes in the diff. It should be in a separate branch, or at least in a separate commit. Will still approve now though.

Revision history for this message
Kevin Nasto (silverdrake11) wrote :

In the haproxy-config.yaml why is hashid-databases on 8080, same port as the appserver? Is it meant to be the same service?

Revision history for this message
Kevin Nasto (silverdrake11) wrote :

Per offline Mattermost thread with @mitchburton, we decided it would be best to install the landscape-hashids deb on first install, but not on upgrade, so please add this otherwise LGTM

review: Needs Fixing
bf0ced8... by Bill Kronholm

Only install landscape-hashids on install.

Revision history for this message
Bill Kronholm (wck0) wrote :

Updated. You can see the most recent changes in src/charm.py and src/haproxy-config.yaml

Here is a diff for the latest commit:

diff --git a/src/charm.py b/src/charm.py
index 719edb3..e1f2587 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -74,7 +74,6 @@ LANDSCAPE_PACKAGES = (
     "landscape-server",
     "landscape-client",
     "landscape-common",
- "landscape-hashids",
 )

 DEFAULT_SERVICES = (
diff --git a/src/haproxy-config.yaml b/src/haproxy-config.yaml
index 9be1ddc..ac0475e 100644
--- a/src/haproxy-config.yaml
+++ b/src/haproxy-config.yaml
@@ -55,8 +55,6 @@ ports:
   message-server: 8090
   api: 9080
   package-upload: 9100
- pppa-proxy: 9298
- hashid-database: 8080

 server_options:
   - check

Revision history for this message
Kevin Nasto (silverdrake11) wrote :

LGTM +1

review: Approve

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 86763ce..5832b12 100644
3--- a/README.md
4+++ b/README.md
5@@ -69,6 +69,5 @@ When developing the charm, here's a quick way to test out changes as
6 they would be deployed by `landscape-scalable`:
7
8 ```bash
9-charmcraft pack
10-juju deploy ./bundle.yaml
11-```
12\ No newline at end of file
13+make build
14+```
15diff --git a/src/charm.py b/src/charm.py
16index 8665696..e1f2587 100755
17--- a/src/charm.py
18+++ b/src/charm.py
19@@ -21,31 +21,48 @@ from subprocess import CalledProcessError, check_call
20 import yaml
21
22 from charms.operator_libs_linux.v0 import apt
23-from charms.operator_libs_linux.v0.apt import (
24- PackageError, PackageNotFoundError)
25+from charms.operator_libs_linux.v0.apt import PackageError, PackageNotFoundError
26 from charms.operator_libs_linux.v0.passwd import group_exists, user_exists
27 from charms.operator_libs_linux.v0.systemd import service_reload
28
29 from ops.charm import (
30- ActionEvent, CharmBase, InstallEvent, LeaderElectedEvent,
31- LeaderSettingsChangedEvent, RelationChangedEvent, RelationJoinedEvent,
32- UpdateStatusEvent)
33+ ActionEvent,
34+ CharmBase,
35+ InstallEvent,
36+ LeaderElectedEvent,
37+ LeaderSettingsChangedEvent,
38+ RelationChangedEvent,
39+ RelationDepartedEvent,
40+ RelationJoinedEvent,
41+ UpdateStatusEvent,
42+)
43 from ops.framework import StoredState
44 from ops.main import main
45 from ops.model import (
46- ActiveStatus, BlockedStatus, Relation, MaintenanceStatus, WaitingStatus)
47+ ActiveStatus,
48+ BlockedStatus,
49+ Relation,
50+ MaintenanceStatus,
51+ WaitingStatus,
52+)
53
54 from settings_files import (
55- DEFAULT_POSTGRES_PORT, configure_for_deployment_mode, merge_service_conf,
56- prepend_default_settings, update_db_conf, update_default_settings, update_service_conf,
57- write_license_file, write_ssl_cert)
58+ DEFAULT_POSTGRES_PORT,
59+ configure_for_deployment_mode,
60+ merge_service_conf,
61+ prepend_default_settings,
62+ update_db_conf,
63+ update_default_settings,
64+ update_service_conf,
65+ write_license_file,
66+ write_ssl_cert,
67+)
68
69 logger = logging.getLogger(__name__)
70
71 DEBCONF_SET_SELECTIONS = "/usr/bin/debconf-set-selections"
72 DPKG_RECONFIGURE = "/usr/sbin/dpkg-reconfigure"
73-HAPROXY_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
74- "haproxy-config.yaml")
75+HAPROXY_CONFIG_FILE = os.path.join(os.path.dirname(__file__), "haproxy-config.yaml")
76 LSCTL = "/usr/bin/lsctl"
77 NRPE_D_DIR = "/etc/nagios/nrpe.d"
78 POSTFIX_CF = "/etc/postfix/main.cf"
79@@ -57,7 +74,6 @@ LANDSCAPE_PACKAGES = (
80 "landscape-server",
81 "landscape-client",
82 "landscape-common",
83- "landscape-hashids"
84 )
85
86 DEFAULT_SERVICES = (
87@@ -71,6 +87,7 @@ DEFAULT_SERVICES = (
88 LEADER_SERVICES = (
89 "landscape-package-search",
90 "landscape-package-upload",
91+ "landscape-hashid-databases",
92 )
93
94 OPENID_CONFIG_VALS = (
95@@ -100,47 +117,59 @@ class LandscapeServerCharm(CharmBase):
96 self.framework.observe(self.on.update_status, self._update_status)
97
98 # Relations
99- self.framework.observe(self.on.db_relation_joined,
100- self._db_relation_changed)
101- self.framework.observe(self.on.db_relation_changed,
102- self._db_relation_changed)
103- self.framework.observe(self.on.amqp_relation_joined,
104- self._amqp_relation_joined)
105- self.framework.observe(self.on.amqp_relation_changed,
106- self._amqp_relation_changed)
107- self.framework.observe(self.on.website_relation_joined,
108- self._website_relation_joined)
109- self.framework.observe(self.on.website_relation_changed,
110- self._website_relation_changed)
111- self.framework.observe(self.on.nrpe_external_master_relation_joined,
112- self._nrpe_external_master_relation_joined)
113- self.framework.observe(self.on.application_dashboard_relation_joined,
114- self._application_dashboard_relation_joined)
115+ self.framework.observe(self.on.db_relation_joined, self._db_relation_changed)
116+ self.framework.observe(self.on.db_relation_changed, self._db_relation_changed)
117+ self.framework.observe(self.on.amqp_relation_joined, self._amqp_relation_joined)
118+ self.framework.observe(
119+ self.on.amqp_relation_changed, self._amqp_relation_changed
120+ )
121+ self.framework.observe(
122+ self.on.website_relation_joined, self._website_relation_joined
123+ )
124+ self.framework.observe(
125+ self.on.website_relation_changed, self._website_relation_changed
126+ )
127+ self.framework.observe(
128+ self.on.website_relation_departed, self._website_relation_departed
129+ )
130+ self.framework.observe(
131+ self.on.nrpe_external_master_relation_joined,
132+ self._nrpe_external_master_relation_joined,
133+ )
134+ self.framework.observe(
135+ self.on.application_dashboard_relation_joined,
136+ self._application_dashboard_relation_joined,
137+ )
138
139 # Leadership/peering
140 self.framework.observe(self.on.leader_elected, self._leader_elected)
141- self.framework.observe(self.on.leader_settings_changed,
142- self._leader_settings_changed)
143- self.framework.observe(self.on.replicas_relation_joined,
144- self._on_replicas_relation_joined)
145- self.framework.observe(self.on.replicas_relation_changed,
146- self._on_replicas_relation_changed)
147+ self.framework.observe(
148+ self.on.leader_settings_changed, self._leader_settings_changed
149+ )
150+ self.framework.observe(
151+ self.on.replicas_relation_joined, self._on_replicas_relation_joined
152+ )
153+ self.framework.observe(
154+ self.on.replicas_relation_changed, self._on_replicas_relation_changed
155+ )
156
157 # Actions
158 self.framework.observe(self.on.pause_action, self._pause)
159 self.framework.observe(self.on.resume_action, self._resume)
160 self.framework.observe(self.on.upgrade_action, self._upgrade)
161- self.framework.observe(self.on.migrate_schema_action,
162- self._migrate_schema)
163- self.framework.observe(self.on.hash_id_databases_action,
164- self._hash_id_databases)
165+ self.framework.observe(self.on.migrate_schema_action, self._migrate_schema)
166+ self.framework.observe(
167+ self.on.hash_id_databases_action, self._hash_id_databases
168+ )
169
170 # State
171- self._stored.set_default(ready={
172- "db": False,
173- "amqp": False,
174- "haproxy": False,
175- })
176+ self._stored.set_default(
177+ ready={
178+ "db": False,
179+ "amqp": False,
180+ "haproxy": False,
181+ }
182+ )
183 self._stored.set_default(leader_ip="")
184 self._stored.set_default(running=False)
185 self._stored.set_default(paused=False)
186@@ -173,10 +202,8 @@ class LandscapeServerCharm(CharmBase):
187 # Write the license file, if it exists.
188 license_file = self.model.config.get("license_file")
189 if license_file:
190- self.unit.status = MaintenanceStatus(
191- "Writing Landscape license file")
192- write_license_file(
193- license_file, self.landscape_uid, self.root_gid)
194+ self.unit.status = MaintenanceStatus("Writing Landscape license file")
195+ write_license_file(license_file, self.landscape_uid, self.root_gid)
196 self.unit.status = WaitingStatus("Waiting on relations")
197
198 smtp_relay_host = self.model.config.get("smtp_relay_host")
199@@ -189,10 +216,12 @@ class LandscapeServerCharm(CharmBase):
200 for relation in haproxy_relations:
201 self._update_haproxy_connection(relation)
202
203- if any(self.model.config.get(v) for v in OPENID_CONFIG_VALS) \
204- and any(self.model.config.get(v) for v in OIDC_CONFIG_VALS):
205+ if any(self.model.config.get(v) for v in OPENID_CONFIG_VALS) and any(
206+ self.model.config.get(v) for v in OIDC_CONFIG_VALS
207+ ):
208 self.unit.status = BlockedStatus(
209- "OpenID and OIDC configurations are mutually exclusive")
210+ "OpenID and OIDC configurations are mutually exclusive"
211+ )
212 else:
213 self._configure_openid()
214 self._configure_oidc()
215@@ -200,11 +229,13 @@ class LandscapeServerCharm(CharmBase):
216 # Update root_url, if provided
217 root_url = self.model.config.get("root_url")
218 if root_url:
219- update_service_conf({
220- "global": {"root-url": root_url},
221- "api": {"root-url": root_url},
222- "package-upload": {"root-url": root_url},
223- })
224+ update_service_conf(
225+ {
226+ "global": {"root-url": root_url},
227+ "api": {"root-url": root_url},
228+ "package-upload": {"root-url": root_url},
229+ }
230+ )
231
232 config_host = self.model.config.get("db_host")
233 schema_password = self.model.config.get("db_schema_password")
234@@ -262,8 +293,7 @@ class LandscapeServerCharm(CharmBase):
235 license_file = self.model.config.get("license_file")
236
237 if license_file:
238- self.unit.status = MaintenanceStatus(
239- "Writing Landscape license file")
240+ self.unit.status = MaintenanceStatus("Writing Landscape license file")
241 write_license_file(license_file, self.landscape_uid, self.root_gid)
242
243 self.unit.status = ActiveStatus("Unit is ready")
244@@ -283,10 +313,10 @@ class LandscapeServerCharm(CharmBase):
245 return
246
247 if not all(self._stored.ready.values()):
248- waiting_on = [
249- rel for rel, ready in self._stored.ready.items() if not ready]
250+ waiting_on = [rel for rel, ready in self._stored.ready.items() if not ready]
251 self.unit.status = WaitingStatus(
252- "Waiting on relations: {}".format(", ".join(waiting_on)))
253+ "Waiting on relations: {}".format(", ".join(waiting_on))
254+ )
255 return
256
257 if self._stored.running and not restart_services:
258@@ -309,19 +339,23 @@ class LandscapeServerCharm(CharmBase):
259 deployment_mode = self.model.config.get("deployment_mode")
260 is_standalone = deployment_mode == "standalone"
261
262- update_default_settings({
263- "RUN_ALL": "no",
264- "RUN_APISERVER": str(self.model.config["worker_counts"]),
265- "RUN_ASYNC_FRONTEND": "yes",
266- "RUN_JOBHANDLER": "yes",
267- "RUN_APPSERVER": str(self.model.config["worker_counts"]),
268- "RUN_MSGSERVER": str(self.model.config["worker_counts"]),
269- "RUN_PINGSERVER": str(self.model.config["worker_counts"]),
270- "RUN_CRON": "yes" if is_leader else "no",
271- "RUN_PACKAGESEARCH": "yes" if is_leader else "no",
272- "RUN_PACKAGEUPLOADSERVER": "yes" if is_leader and is_standalone else "no",
273- "RUN_PPPA_PROXY": "no",
274- })
275+ update_default_settings(
276+ {
277+ "RUN_ALL": "no",
278+ "RUN_APISERVER": str(self.model.config["worker_counts"]),
279+ "RUN_ASYNC_FRONTEND": "yes",
280+ "RUN_JOBHANDLER": "yes",
281+ "RUN_APPSERVER": str(self.model.config["worker_counts"]),
282+ "RUN_MSGSERVER": str(self.model.config["worker_counts"]),
283+ "RUN_PINGSERVER": str(self.model.config["worker_counts"]),
284+ "RUN_CRON": "yes" if is_leader else "no",
285+ "RUN_PACKAGESEARCH": "yes" if is_leader else "no",
286+ "RUN_PACKAGEUPLOADSERVER": "yes"
287+ if is_leader and is_standalone
288+ else "no",
289+ "RUN_PPPA_PROXY": "no",
290+ }
291+ )
292
293 logger.info("Starting services")
294
295@@ -353,7 +387,7 @@ class LandscapeServerCharm(CharmBase):
296
297 allowed_units = unit_data["allowed-units"].split()
298 if self.unit.name not in allowed_units:
299- logger.info("%s not in allowed_units")
300+ logger.info(f"{self.unit.name} not in allowed_units")
301 self.unit.status = ActiveStatus("Unit is ready")
302 self._update_ready_status()
303 return
304@@ -393,8 +427,13 @@ class LandscapeServerCharm(CharmBase):
305 else:
306 user = unit_data["user"]
307
308- update_db_conf(host=host, port=port, user=user, password=password,
309- schema_password=schema_password)
310+ update_db_conf(
311+ host=host,
312+ port=port,
313+ user=user,
314+ password=password,
315+ schema_password=schema_password,
316+ )
317
318 if not self._migrate_schema_bootstrap():
319 return
320@@ -424,10 +463,12 @@ class LandscapeServerCharm(CharmBase):
321 self._stored.ready["amqp"] = False
322 self.unit.status = MaintenanceStatus("Setting up amqp connection")
323
324- event.relation.data[self.unit].update({
325- "username": "landscape",
326- "vhost": "landscape",
327- })
328+ event.relation.data[self.unit].update(
329+ {
330+ "username": "landscape",
331+ "vhost": "landscape",
332+ }
333+ )
334
335 def _amqp_relation_changed(self, event):
336 unit_data = event.relation.data[event.unit]
337@@ -442,12 +483,14 @@ class LandscapeServerCharm(CharmBase):
338 if isinstance(hostname, list):
339 hostname = ",".join(hostname)
340
341- update_service_conf({
342- "broker": {
343- "host": hostname,
344- "password": password,
345+ update_service_conf(
346+ {
347+ "broker": {
348+ "host": hostname,
349+ "password": password,
350+ }
351 }
352- })
353+ )
354
355 self._stored.ready["amqp"] = True
356 self.unit.status = ActiveStatus("Unit is ready")
357@@ -460,11 +503,13 @@ class LandscapeServerCharm(CharmBase):
358 if not self.model.config.get("root_url"):
359 url = f'https://{event.relation.data[event.unit]["public-address"]}/'
360 self._stored.default_root_url = url
361- update_service_conf({
362- "global": {"root-url": url},
363- "api": {"root-url": url},
364- "package-upload": {"root-url": url},
365- })
366+ update_service_conf(
367+ {
368+ "global": {"root-url": url},
369+ "api": {"root-url": url},
370+ "package-upload": {"root-url": url},
371+ }
372+ )
373
374 self._update_ready_status()
375
376@@ -480,7 +525,8 @@ class LandscapeServerCharm(CharmBase):
377 if ssl_cert != "DEFAULT" and ssl_key == "":
378 # We have a cert but no key, this is an error.
379 self.unit.status = BlockedStatus(
380- "`ssl_cert` is specified but `ssl_key` is missing")
381+ "`ssl_cert` is specified but `ssl_key` is missing"
382+ )
383 return
384
385 if ssl_cert != "DEFAULT":
386@@ -490,8 +536,8 @@ class LandscapeServerCharm(CharmBase):
387 ssl_cert = b64encode(ssl_cert + b"\n" + ssl_key)
388 except binascii.Error:
389 self.unit.status = BlockedStatus(
390- "Unable to decode `ssl_cert` or `ssl_key` - must be "
391- "b64-encoded")
392+ "Unable to decode `ssl_cert` or `ssl_key` - must be " "b64-encoded"
393+ )
394 return
395
396 with open(HAPROXY_CONFIG_FILE) as haproxy_config_file:
397@@ -501,68 +547,83 @@ class LandscapeServerCharm(CharmBase):
398 https_service = haproxy_config["https_service"]
399 https_service["crts"] = [ssl_cert]
400
401- if self.unit.is_leader():
402- https_service["service_options"].extend(
403- haproxy_config["leader_service_options"])
404-
405 server_ip = relation.data[self.unit]["private-address"]
406 unit_name = self.unit.name.replace("/", "-")
407 worker_counts = self.model.config["worker_counts"]
408
409 (appservers, pingservers, message_servers, api_servers) = [
410- [(
411- f"landscape-{name}-{unit_name}-{i}",
412- server_ip,
413- haproxy_config["ports"][name] + i,
414- haproxy_config["server_options"],
415- ) for i in range(worker_counts)]
416+ [
417+ (
418+ f"landscape-{name}-{unit_name}-{i}",
419+ server_ip,
420+ haproxy_config["ports"][name] + i,
421+ haproxy_config["server_options"],
422+ )
423+ for i in range(worker_counts)
424+ ]
425 for name in ("appserver", "pingserver", "message-server", "api")
426 ]
427
428 # There should only ever be one package-upload-server service.
429- package_upload_servers = [(
430- f"landscape-package-upload-{unit_name}-0",
431- server_ip,
432- haproxy_config["ports"]["package-upload"],
433- haproxy_config["server_options"],
434- )]
435+ package_upload_servers = [
436+ (
437+ f"landscape-package-upload-{unit_name}-0",
438+ server_ip,
439+ haproxy_config["ports"]["package-upload"],
440+ haproxy_config["server_options"],
441+ )
442+ ]
443
444 http_service["servers"] = appservers
445- http_service["backends"] = [{
446- "backend_name": "landscape-ping",
447- "servers": pingservers,
448- }]
449+ http_service["backends"] = [
450+ {
451+ "backend_name": "landscape-ping",
452+ "servers": pingservers,
453+ }
454+ ]
455 https_service["servers"] = appservers
456- https_service["backends"] = [{
457- "backend_name": "landscape-message",
458- "servers": message_servers,
459- }, {
460- "backend_name": "landscape-api",
461- "servers": api_servers,
462- }]
463-
464- if self.unit.is_leader():
465- https_service["backends"].append({
466+ https_service["backends"] = [
467+ {
468+ "backend_name": "landscape-message",
469+ "servers": message_servers,
470+ },
471+ {
472+ "backend_name": "landscape-api",
473+ "servers": api_servers,
474+ },
475+ # Only the leader should have servers for the landscape-package-upload
476+ # and landscape-hashid-databases backends. However, when the leader
477+ # is lost, haproxy will fail as the service options will reference
478+ # a (no longer) existing backend. To prevent that, all units should
479+ # declare all backends, even if a unit should not have any servers on
480+ # a specific backend.
481+ {
482 "backend_name": "landscape-package-upload",
483- "servers": package_upload_servers,
484- })
485+ "servers": package_upload_servers if self.unit.is_leader() else [],
486+ },
487+ {
488+ "backend_name": "landscape-hashid-databases",
489+ "servers": appservers if self.unit.is_leader() else [],
490+ },
491+ ]
492
493 error_files_location = haproxy_config["error_files"]["location"]
494 error_files = []
495 for code, filename in haproxy_config["error_files"]["files"].items():
496 error_file_path = os.path.join(error_files_location, filename)
497 with open(error_file_path, "rb") as error_file:
498- error_files.append({
499- "http_status": code,
500- "content": b64encode(error_file.read())
501- })
502+ error_files.append(
503+ {"http_status": code, "content": b64encode(error_file.read())}
504+ )
505
506 http_service["error_files"] = error_files
507 https_service["error_files"] = error_files
508
509- relation.data[self.unit].update({
510- "services": yaml.safe_dump([http_service, https_service])
511- })
512+ relation.data[self.unit].update(
513+ {
514+ "services": yaml.safe_dump([http_service, https_service]),
515+ }
516+ )
517
518 self._stored.ready["haproxy"] = True
519
520@@ -591,13 +652,14 @@ class LandscapeServerCharm(CharmBase):
521 write_ssl_cert(haproxy_ssl_cert)
522
523 self.unit.status = ActiveStatus("Unit is ready")
524-
525 self._update_haproxy_connection(event.relation)
526
527 self._update_ready_status()
528
529- def _nrpe_external_master_relation_joined(
530- self, event: RelationJoinedEvent) -> None:
531+ def _website_relation_departed(self, event: RelationDepartedEvent) -> None:
532+ event.relation.data[self.unit].update({"services": ""})
533+
534+ def _nrpe_external_master_relation_joined(self, event: RelationJoinedEvent) -> None:
535 self._update_nrpe_checks(event.relation)
536
537 def _update_nrpe_checks(self, relation: Relation):
538@@ -613,16 +675,16 @@ class LandscapeServerCharm(CharmBase):
539 monitors = {
540 "monitors": {
541 "remote": {
542- "nrpe": {
543- s: {"command": f"check_{s}"} for s in services_to_add
544- },
545+ "nrpe": {s: {"command": f"check_{s}"} for s in services_to_add},
546 },
547 },
548 }
549
550- relation.data[self.unit].update({
551- "monitors": yaml.safe_dump(monitors),
552- })
553+ relation.data[self.unit].update(
554+ {
555+ "monitors": yaml.safe_dump(monitors),
556+ }
557+ )
558
559 if not os.path.exists(NRPE_D_DIR):
560 logger.debug("NRPE directories not ready")
561@@ -636,12 +698,14 @@ class LandscapeServerCharm(CharmBase):
562 continue
563
564 with open(cfg_filename, "w") as cfg_fp:
565- cfg_fp.write(f"""# check {service}
566+ cfg_fp.write(
567+ f"""# check {service}
568 # The following header was added by the landscape-server charm
569 # Modifying it will affect nagios monitoring and alerting
570 # servicegroups: juju
571 command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service}
572-""")
573+"""
574+ )
575
576 for service in services_to_remove:
577 service_cfg = service.replace("-", "_")
578@@ -662,7 +726,8 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
579
580 if not root_url:
581 root_url = "https://" + str(
582- self.model.get_binding(event.relation).network.bind_address)
583+ self.model.get_binding(event.relation).network.bind_address
584+ )
585
586 site_name = self.model.config.get("site_name")
587 if site_name:
588@@ -679,13 +744,15 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
589 else:
590 icon_data = None
591
592- event.relation.data[self.app].update({
593- "name": "Landscape",
594- "url": root_url,
595- "subtitle": subtitle,
596- "group": group,
597- "icon": icon_data,
598- })
599+ event.relation.data[self.app].update(
600+ {
601+ "name": "Landscape",
602+ "url": root_url,
603+ "subtitle": subtitle,
604+ "group": group,
605+ "icon": icon_data,
606+ }
607+ )
608
609 def _leader_elected(self, event: LeaderElectedEvent) -> None:
610 # Just because we received this event does not mean we are
611@@ -698,16 +765,17 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
612 ip = str(self.model.get_binding(peer_relation).network.bind_address)
613 peer_relation.data[self.app].update({"leader-ip": ip})
614
615- update_service_conf({
616- "package-search": {
617- "host": "localhost",
618- },
619- })
620+ update_service_conf(
621+ {
622+ "package-search": {
623+ "host": "localhost",
624+ },
625+ }
626+ )
627
628 self._leader_changed()
629
630- def _leader_settings_changed(
631- self, event: LeaderSettingsChangedEvent) -> None:
632+ def _leader_settings_changed(self, event: LeaderSettingsChangedEvent) -> None:
633 # Just because we received this event does not mean we are
634 # guaranteed to be a follower by the time we process it. See
635 # https://juju.is/docs/sdk/leader-elected-event
636@@ -717,11 +785,13 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
637 leader_ip = peer_relation.data[self.app].get("leader-ip")
638
639 if leader_ip:
640- update_service_conf({
641- "package-search": {
642- "host": leader_ip,
643- },
644- })
645+ update_service_conf(
646+ {
647+ "package-search": {
648+ "host": leader_ip,
649+ },
650+ }
651+ )
652
653 self._leader_changed()
654
655@@ -736,9 +806,10 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
656 for relation in nrpe_relations:
657 self._update_nrpe_checks(relation)
658
659- haproxy_relations = self.model.relations.get("website", [])
660- for relation in haproxy_relations:
661- self._update_haproxy_connection(relation)
662+ if self.unit.is_leader():
663+ haproxy_relations = self.model.relations.get("website", [])
664+ for relation in haproxy_relations:
665+ self._update_haproxy_connection(relation)
666
667 self._update_ready_status(restart_services=True)
668
669@@ -749,13 +820,17 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
670
671 event.relation.data[self.unit].update({"unit-data": self.unit.name})
672
673- def _on_replicas_relation_changed(
674- self, event: RelationChangedEvent) -> None:
675+ def _on_replicas_relation_changed(self, event: RelationChangedEvent) -> None:
676 leader_ip_value = event.relation.data[self.app].get("leader-ip")
677
678 if leader_ip_value and leader_ip_value != self._stored.leader_ip:
679 self._stored.leader_ip = leader_ip_value
680
681+ if self.unit.is_leader():
682+ haproxy_relations = self.model.relations.get("website", [])
683+ for relation in haproxy_relations:
684+ self._update_haproxy_connection(relation)
685+
686 def _configure_smtp(self, relay_host: str) -> None:
687
688 # Rewrite postfix config.
689@@ -789,27 +864,32 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
690 none_count = oidc_vals.count(None)
691
692 if none_count == 0:
693- update_service_conf({
694- "landscape": {
695- "oidc-issuer": oidc_issuer,
696- "oidc-client-id": oidc_client_id,
697- "oidc-client-secret": oidc_client_secret,
698- "oidc-logout-url": oidc_logout_url,
699- },
700- })
701+ update_service_conf(
702+ {
703+ "landscape": {
704+ "oidc-issuer": oidc_issuer,
705+ "oidc-client-id": oidc_client_id,
706+ "oidc-client-secret": oidc_client_secret,
707+ "oidc-logout-url": oidc_logout_url,
708+ },
709+ }
710+ )
711 elif none_count == 1 and oidc_logout_url is None:
712 # Only the logout url is optional.
713- update_service_conf({
714- "landscape": {
715- "oidc-issuer": oidc_issuer,
716- "oidc-client-id": oidc_client_id,
717- "oidc-client-secret": oidc_client_secret,
718- },
719- })
720+ update_service_conf(
721+ {
722+ "landscape": {
723+ "oidc-issuer": oidc_issuer,
724+ "oidc-client-id": oidc_client_id,
725+ "oidc-client-secret": oidc_client_secret,
726+ },
727+ }
728+ )
729 elif none_count < 4:
730 self.unit.status = BlockedStatus(
731 "OIDC connect config requires at least 'oidc_issuer', "
732- "'oidc_client_id', and 'oidc_client_secret' values")
733+ "'oidc_client_id', and 'oidc_client_secret' values"
734+ )
735 return
736
737 self.unit.status = WaitingStatus("Waiting on relations")
738@@ -821,17 +901,20 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
739 openid_logout_url = self.model.config.get("openid_logout_url")
740
741 if openid_provider_url and openid_logout_url:
742- update_service_conf({
743- "landscape": {
744- "openid-provider-url": openid_provider_url,
745- "openid-logout-url": openid_logout_url,
746- },
747- })
748+ update_service_conf(
749+ {
750+ "landscape": {
751+ "openid-provider-url": openid_provider_url,
752+ "openid-logout-url": openid_logout_url,
753+ },
754+ }
755+ )
756 self.unit.status = WaitingStatus("Waiting on relations")
757 elif openid_provider_url or openid_logout_url:
758 self.unit.status = BlockedStatus(
759 "OpenID configuration requires both 'openid_provider_url' and "
760- "'openid_logout_url'")
761+ "'openid_logout_url'"
762+ )
763
764 def _bootstrap_account(self):
765 """If admin account details are provided, create admin"""
766@@ -895,8 +978,7 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
767 try:
768 check_call([LSCTL, "stop"])
769 except CalledProcessError as e:
770- logger.error("Stopping services failed with return code %d",
771- e.returncode)
772+ logger.error("Stopping services failed with return code %d", e.returncode)
773 self.unit.status = BlockedStatus("Failed to stop services")
774 event.fail("Failed to stop services")
775 else:
776@@ -908,14 +990,12 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
777 self.unit.status = MaintenanceStatus("Starting services")
778 event.log("Starting services")
779
780- start_result = subprocess.run([LSCTL, "start"], capture_output=True,
781- text=True)
782+ start_result = subprocess.run([LSCTL, "start"], capture_output=True, text=True)
783
784 try:
785 check_call([LSCTL, "status"])
786 except CalledProcessError as e:
787- logger.error("Starting services failed with return code %d",
788- e.returncode)
789+ logger.error("Starting services failed with return code %d", e.returncode)
790 logger.error("Failed to start services: %s", start_result.stdout)
791 self.unit.status = MaintenanceStatus("Stopping services")
792 subprocess.run([LSCTL, "stop"])
793@@ -929,8 +1009,10 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
794
795 def _upgrade(self, event: ActionEvent) -> None:
796 if self._stored.running:
797- event.fail("Cannot upgrade while running. Please run action "
798- "'pause' prior to upgrade")
799+ event.fail(
800+ "Cannot upgrade while running. Please run action "
801+ "'pause' prior to upgrade"
802+ )
803 return
804
805 prev_status = self.unit.status
806@@ -947,7 +1029,9 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
807 installed = apt.DebianPackage.from_installed_package(package)
808 event.log(f"Upgraded to {installed.version}...")
809 except PackageNotFoundError as e:
810- logger.error(f"Could not upgrade package {package}. Reason: {e.message}")
811+ logger.error(
812+ f"Could not upgrade package {package}. Reason: {e.message}"
813+ )
814 event.fail(f"Could not upgrade package {package}. Reason: {e.message}")
815 self.unit.status = BlockedStatus("Failed to upgrade packages")
816 return
817@@ -956,8 +1040,10 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
818
819 def _migrate_schema(self, event: ActionEvent) -> None:
820 if self._stored.running:
821- event.fail("Cannot migrate schema while running. Please run action"
822- " 'pause' prior to migration")
823+ event.fail(
824+ "Cannot migrate schema while running. Please run action"
825+ " 'pause' prior to migration"
826+ )
827 return
828
829 prev_status = self.unit.status
830@@ -967,10 +1053,8 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
831 try:
832 subprocess.run([SCHEMA_SCRIPT], check=True, text=True)
833 except CalledProcessError as e:
834- logger.error("Schema migration failed with error code %s",
835- e.returncode)
836- event.fail("Schema migration failed with error code %s",
837- e.returncode)
838+ logger.error("Schema migration failed with error code %s", e.returncode)
839+ event.fail("Schema migration failed with error code %s", e.returncode)
840 self.unit.status = BlockedStatus("Failed schema migration")
841 else:
842 self.unit.status = prev_status
843@@ -981,7 +1065,9 @@ command[check_{service}]=/usr/local/lib/nagios/plugins/check_systemd.py {service
844 event.log("Running hash_id_databases")
845
846 try:
847- subprocess.run(["sudo", "-u", "landscape", HASH_ID_DATABASES], check=True, text=True)
848+ subprocess.run(
849+ ["sudo", "-u", "landscape", HASH_ID_DATABASES], check=True, text=True
850+ )
851 except CalledProcessError as e:
852 logger.error("Hashing ID databases failed with error code %s", e.returncode)
853 event.fail("Hashing ID databases failed with error code %s", e.returncode)
854diff --git a/src/haproxy-config.yaml b/src/haproxy-config.yaml
855index e986f53..ac0475e 100644
856--- a/src/haproxy-config.yaml
857+++ b/src/haproxy-config.yaml
858@@ -34,11 +34,11 @@ https_service:
859 - use_backend landscape-message if attachment
860 - use_backend landscape-api if api
861 - use_backend landscape-ping if ping
862-
863-leader_service_options:
864- - acl package-upload path_beg -i /upload
865- - use_backend landscape-package-upload if package-upload
866- - reqrep ^([^\ ]*)\ /upload/(.*) \1\ /\2
867+ - acl hashids path_beg -i /hash-id-databases
868+ - use_backend landscape-hashid-databases if hashids
869+ - acl package-upload path_beg -i /upload
870+ - use_backend landscape-package-upload if package-upload
871+ - reqrep ^([^\ ]*)\ /upload/(.*) \1\ /\2
872
873 error_files:
874 location: /opt/canonical/landscape/canonical/landscape/offline
875@@ -55,11 +55,10 @@ ports:
876 message-server: 8090
877 api: 9080
878 package-upload: 9100
879- pppa-proxy: 9298
880
881 server_options:
882 - check
883 - inter 5000
884 - rise 2
885 - fall 5
886- - maxconn 50
887\ No newline at end of file
888+ - maxconn 50

Subscribers

People subscribed via source and target branches

to all changes: