Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ The charm is a **machine charm** targeting `ubuntu@24.04`. It deploys the Error
| `enable_daisy` | `true` | Enable the daisy crash receiver service |
| `enable_retracer` | `true` | Enable the retracer service |
| `enable_timers` | `true` | Enable the housekeeping timer units |
| `enable_web` | `true` | Enable the errors web frontend service |
| `enable_errors` | `true` | Enable the errors web frontend service |
| `configuration` | `""` | Full Python configuration file content for the app |
| `retracer_failed_queue` | `false` | Whether the retracer listens on the failed retracing queue|

Expand Down
78 changes: 70 additions & 8 deletions charm/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

import ops
from charms.haproxy.v1.haproxy_route import HaproxyRouteRequirer
from charms.traefik_k8s.v2.ingress import (
IngressPerAppReadyEvent,
)
from charms.traefik_k8s.v2.ingress import (
IngressPerAppRequirer as IngressRequirer,
)

from errortracker import ErrorTracker

Expand All @@ -20,24 +26,44 @@ class ErrorTrackerCharm(ops.CharmBase):
def __init__(self, *args):
super().__init__(*args)
self._error_tracker = ErrorTracker()

self.ingress_daisy = IngressRequirer(
self,
port=self._error_tracker.daisy_port,
strip_prefix=True,
relation_name="ingress_daisy",
)
self.ingress_errors = IngressRequirer(
self,
port=self._error_tracker.errors_port,
strip_prefix=True,
relation_name="ingress_errors",
)

self.route_daisy = HaproxyRouteRequirer(
self,
service="daisy",
ports=[self._error_tracker.daisy_port],
relation_name="route_daisy",
)
self.route_web = HaproxyRouteRequirer(
self.route_errors = HaproxyRouteRequirer(
self,
service="web",
ports=[self._error_tracker.web_port],
relation_name="route_web",
service="errors",
ports=[self._error_tracker.errors_port],
relation_name="route_errors",
)

self.framework.observe(self.on.start, self._on_start)
self.framework.observe(self.on.install, self._on_install)
self.framework.observe(self.on.upgrade_charm, self._on_install)
self.framework.observe(self.on.config_changed, self._on_config_changed)

self.framework.observe(self.route_daisy.on.ready, self._on_endpoints_ready)
self.framework.observe(self.route_errors.on.ready, self._on_endpoints_ready)

self.framework.observe(self.ingress_daisy.on.ready, self._on_ingress_ready)
self.framework.observe(self.ingress_errors.on.ready, self._on_ingress_ready)

def _on_start(self, event: ops.StartEvent):
"""Handle start event."""
self.unit.status = ops.ActiveStatus()
Expand All @@ -58,7 +84,7 @@ def _on_config_changed(self, event: ops.ConfigChangedEvent):
enable_daisy = self.config.get("enable_daisy")
enable_retracer = self.config.get("enable_retracer")
enable_timers = self.config.get("enable_timers")
enable_web = self.config.get("enable_web")
enable_errors = self.config.get("enable_errors")

config = self.config.get("configuration")

Expand All @@ -76,14 +102,50 @@ def _on_config_changed(self, event: ops.ConfigChangedEvent):
self._error_tracker.configure_retracer(self.config.get("retracer_failed_queue"))
if enable_timers:
self._error_tracker.configure_timers()
if enable_web:
self._error_tracker.configure_web()
ports.append(self._error_tracker.web_port)
if enable_errors:
self._error_tracker.configure_errors()
ports.append(self._error_tracker.errors_port)
self.unit.set_ports(*ports)

self.unit.set_workload_version(self._error_tracker.get_version())

self.unit.status = ops.ActiveStatus("Ready")

def _on_ingress_ready(self, event: IngressPerAppReadyEvent):
logger.info("ingress URL: %s", event.url)
daisy_hostname = self.config.get("daisy_hostname")
errors_hostname = self.config.get("errors_hostname")
enable_daisy = self.config.get("enable_daisy")
enable_errors = self.config.get("enable_errors")
if enable_daisy:
self.ingress_daisy.provide_ingress_requirements(
port=self._error_tracker.daisy_port,
host=daisy_hostname,
)
if enable_errors:
self.ingress_errors.provide_ingress_requirements(
port=self._error_tracker.errors_port,
host=errors_hostname,
)

def _on_endpoints_ready(self, event):
daisy_hostname = self.config.get("daisy_hostname")
errors_hostname = self.config.get("errors_hostname")
enable_daisy = self.config.get("enable_daisy")
enable_errors = self.config.get("enable_errors")
if enable_daisy:
self.route_daisy.provide_haproxy_route_requirements(
service="daisy",
ports=[self._error_tracker.daisy_port],
hostname=daisy_hostname,
)
if enable_errors:
self.route_errors.provide_haproxy_route_requirements(
service="errors",
ports=[self._error_tracker.errors_port],
hostname=errors_hostname,
)


if __name__ == "__main__": # pragma: nocover
ops.main(ErrorTrackerCharm) # type: ignore
20 changes: 10 additions & 10 deletions charm/errortracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ def __init__(self):
self.enable_retracer = True
self.enable_timers = True
self.enable_daisy = True
self.enable_web = True
self.enable_errors = True
self.daisy_port = 8000
self.web_port = 9000
self.errors_port = 9000

def install(self):
self._install_deps()
Expand Down Expand Up @@ -237,9 +237,9 @@ def configure_timers(self):
"*-*-* 06:45:00", # every day at 06:45
)

def configure_web(self):
logger.info("Configuring web")
logger.info("Installing additional web dependencies")
def configure_errors(self):
logger.info("Configuring errors")
logger.info("Installing additional errors dependencies")
check_call(
[
"apt-get",
Expand All @@ -256,10 +256,10 @@ def configure_web(self):
)
systemd_unit_location = Path("/") / "etc" / "systemd" / "system"
systemd_unit_location.mkdir(parents=True, exist_ok=True)
(systemd_unit_location / "et-web.service").write_text(
(systemd_unit_location / "errors.service").write_text(
f"""
[Unit]
Description=Error Tracker web
Description=Error Tracker errors
After=network.target

[Service]
Expand All @@ -268,7 +268,7 @@ def configure_web(self):
WorkingDirectory={REPO_LOCATION}/src
ExecStartPre={REPO_LOCATION}/src/errors/manage.py migrate --no-input
ExecStartPre={REPO_LOCATION}/src/errors/manage.py collectstatic --no-input --clear
ExecStart=bash -c 'exec uwsgi --plugins python3 --http-socket 0.0.0.0:{self.web_port} --wsgi-file {REPO_LOCATION}/src/errors/wsgi.py --static-map "/static={REPO_LOCATION}/src/static/" --chdir {REPO_LOCATION}/src/ --die-on-term --master --env PYTHONPATH={REPO_LOCATION}/src/ --max-requests 4000 --max-worker-lifetime 21600 --processes "$(($(nproc) * 2))"'
ExecStart=bash -c 'exec uwsgi --plugins python3 --http-socket 0.0.0.0:{self.errors_port} --wsgi-file {REPO_LOCATION}/src/errors/wsgi.py --static-map "/static={REPO_LOCATION}/src/static/" --chdir {REPO_LOCATION}/src/ --die-on-term --master --env PYTHONPATH={REPO_LOCATION}/src/ --max-requests 4000 --max-worker-lifetime 21600 --processes "$(($(nproc) * 2))"'
Restart=always

[Install]
Expand All @@ -279,7 +279,7 @@ def configure_web(self):
check_call(["systemctl", "daemon-reload"])

logger.info("enabling systemd units")
check_call(["systemctl", "enable", "et-web"])
check_call(["systemctl", "enable", "errors"])

logger.info("restarting systemd units")
check_call(["systemctl", "restart", "et-web"])
check_call(["systemctl", "restart", "errors"])
8 changes: 5 additions & 3 deletions charm/tests/integration/test_daisy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
HAPROXY = "haproxy"
SSC = "self-signed-certificates"

external_hostname = "daisy.internal"


def test_deploy(
juju: jubilant.Juju,
Expand All @@ -24,10 +26,11 @@ def test_deploy(
app="daisy",
config={
"configuration": error_tracker_config,
"daisy_hostname": external_hostname,
"enable_daisy": True,
"enable_retracer": False,
"enable_timers": False,
"enable_web": False,
"enable_errors": False,
},
)

Expand All @@ -37,8 +40,7 @@ def test_deploy(


def test_http(juju: jubilant.Juju):
external_hostname = "daisy.internal"
juju.deploy(HAPROXY, channel="2.8/edge", config={"external-hostname": external_hostname})
juju.deploy(HAPROXY, channel="2.8/edge", config={"external-hostname": "haproxy.internal"})
juju.deploy(SSC, channel="1/edge")

juju.integrate(HAPROXY + ":certificates", SSC + ":certificates")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
HAPROXY = "haproxy"
SSC = "self-signed-certificates"

external_hostname = "errors.internal"


def test_deploy(
juju: jubilant.Juju,
Expand All @@ -21,28 +23,28 @@ def test_deploy(
):
juju.deploy(
charm=charm_path,
app="web",
app="errors",
config={
"configuration": error_tracker_config,
"errors_hostname": external_hostname,
"enable_daisy": False,
"enable_retracer": False,
"enable_timers": False,
"enable_web": True,
"enable_errors": True,
},
)

juju.wait(lambda status: jubilant.all_active(status, "web"), timeout=600)
juju.wait(lambda status: jubilant.all_active(status, "errors"), timeout=600)

check_config(juju, amqp, cassandra, swift, "web/0")
check_config(juju, amqp, cassandra, swift, "errors/0")


def test_http(juju: jubilant.Juju):
external_hostname = "errors.internal"
juju.deploy(HAPROXY, channel="2.8/edge", config={"external-hostname": external_hostname})
juju.deploy(HAPROXY, channel="2.8/edge", config={"external-hostname": "haproxy.internal"})
juju.deploy(SSC, channel="1/edge")

juju.integrate(HAPROXY + ":certificates", SSC + ":certificates")
juju.integrate("web:route_web", HAPROXY)
juju.integrate("errors:route_errors", HAPROXY)
juju.wait(lambda status: jubilant.all_active(status, HAPROXY, SSC), timeout=1800)

haproxy_ip = juju.status().apps[HAPROXY].units[f"{HAPROXY}/0"].public_address
Expand Down
2 changes: 1 addition & 1 deletion charm/tests/integration/test_retracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_deploy(
"enable_daisy": False,
"enable_retracer": True,
"enable_timers": False,
"enable_web": False,
"enable_errors": False,
},
)

Expand Down
2 changes: 1 addition & 1 deletion charm/tests/integration/test_timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_deploy(
"enable_daisy": False,
"enable_retracer": False,
"enable_timers": True,
"enable_web": False,
"enable_errors": False,
},
)

Expand Down
28 changes: 24 additions & 4 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ summary: The whole infrastructure collecting and processing crashes for Ubuntu
description: |
The Error Tracker is made of the following components:
* daisy - receiving crashes
* web UI - displaying and visualizing data for developers
* errors - displaying and visualizing data for developers
* timers - for processing and housekeeping tasks
* retracer - to get symbolic stacktraces out of coredumps

Expand Down Expand Up @@ -48,9 +48,21 @@ parts:
charm-libs:
- lib: haproxy.haproxy_route
version: "1"
- lib: traefik_k8s.ingress
version: "2"

config:
options:
daisy_hostname:
description: |
The hostname to use for daisy
default: "daisy.local"
type: string
errors_hostname:
description: |
The hostname to use for errors
default: "errors.local"
type: string
enable_daisy:
description: |
Whether to enable the 'daisy' component of the Error Tracker
Expand All @@ -66,9 +78,9 @@ config:
Whether to enable the 'timers' component of the Error Tracker
default: true
type: boolean
enable_web:
enable_errors:
description: |
Whether to enable the 'web' component of the Error Tracker
Whether to enable the 'errors' component of the Error Tracker
default: true
type: boolean
configuration:
Expand All @@ -87,7 +99,15 @@ requires:
interface: haproxy-route
limit: 1
optional: true
route_web:
route_errors:
interface: haproxy-route
limit: 1
optional: true
ingress_daisy:
interface: ingress
limit: 1
optional: true
ingress_errors:
interface: ingress
limit: 1
optional: true
Loading