Skip to content

Commit

Permalink
add HAProxy integration (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexsander-souza authored Feb 26, 2024
1 parent 8e6eef2 commit 7b41024
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 7 deletions.
2 changes: 2 additions & 0 deletions maas-region-charm/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ peers:
interface: maas_peers

provides:
api:
interface: http
maas-region:
interface: maas_controller
cos-agent:
Expand Down
62 changes: 56 additions & 6 deletions maas-region-charm/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any

import ops
import yaml
from charms.data_platform_libs.v0 import data_interfaces as db
from charms.grafana_agent.v0 import cos_agent
from charms.maas_region_charm.v0 import maas
Expand All @@ -19,10 +20,13 @@
logger = logging.getLogger(__name__)

MAAS_PEER_NAME = "maas-cluster"
MAAS_API_RELATION = "api"
MAAS_DB_NAME = "maas-db"

MAAS_SNAP_CHANNEL = "3.4/stable"

MAAS_PROXY_PORT = 80

MAAS_HTTP_PORT = 5240
MAAS_HTTPS_PORT = 5443
MAAS_REGION_METRICS_PORT = 5239
Expand Down Expand Up @@ -80,6 +84,12 @@ def __init__(self, *args):
self.framework.observe(self.maasdb.on.database_created, self._on_maasdb_created)
self.framework.observe(self.maasdb.on.endpoints_changed, self._on_maasdb_endpoints_changed)

# HAProxy
api_events = self.on[MAAS_API_RELATION]
self.framework.observe(api_events.relation_changed, self._on_api_endpoint_changed)
self.framework.observe(api_events.relation_departed, self._on_api_endpoint_changed)
self.framework.observe(api_events.relation_broken, self._on_api_endpoint_changed)

# COS
self._grafana_agent = cos_agent.COSAgentProvider(
self,
Expand Down Expand Up @@ -138,19 +148,31 @@ def enrollment_token(self) -> str | None:
"""
return MaasHelper.get_maas_secret()

@property
def bind_address(self) -> str | None:
"""Get Unit bind address.
Returns:
str: A single address that the charm's application should bind() to.
"""
if bind := self.model.get_binding("juju-info"):
return str(bind.network.bind_address)
return None

@property
def maas_api_url(self) -> str:
"""Get MAAS API URL.
Returns:
str: The API URL
"""
# FIXME use VIP when HAProxy is used
if bind := self.model.get_binding("juju-info"):
unit_ip = bind.network.bind_address
return f"http://{unit_ip}:{MAAS_HTTP_PORT}/MAAS"
else:
return ""
if relation := self.model.get_relation(MAAS_API_RELATION):
unit = next(iter(relation.units), None)
if unit and (addr := relation.data[unit].get("public-address")):
return f"http://{addr}:{MAAS_PROXY_PORT}/MAAS"
if bind := self.bind_address:
return f"http://{bind}:{MAAS_HTTP_PORT}/MAAS"
return ""

@property
def maas_id(self) -> str | None:
Expand Down Expand Up @@ -223,6 +245,27 @@ def _get_regions(self) -> list[str]:
eps += [addr]
return list(set(eps))

def _update_ha_proxy(self) -> None:
if relation := self.model.get_relation(MAAS_API_RELATION):
app_name = f"api-{self.app.name}"
data = [
{
"service_name": "haproxy_service" if MAAS_PROXY_PORT == 80 else app_name,
"service_host": "0.0.0.0",
"service_port": MAAS_PROXY_PORT,
"service_options": ["mode http", "balance leastconn"],
"servers": [
(
f"{app_name}-{self.unit.name.replace('/', '-')}",
self.bind_address,
MAAS_HTTP_PORT,
[],
)
],
}
]
relation.data[self.unit]["services"] = yaml.safe_dump(data)

def _on_start(self, _event: ops.StartEvent) -> None:
"""Handle the MAAS controller startup.
Expand Down Expand Up @@ -296,6 +339,13 @@ def _on_maasdb_endpoints_changed(self, event: db.DatabaseEndpointsChangedEvent)
logger.info(f"DSN: {conn}")
self._initialize_maas()

def _on_api_endpoint_changed(self, event: ops.RelationEvent) -> None:
logger.info(event)
self._update_ha_proxy()
self._initialize_maas()
if self.unit.is_leader():
self._publish_tokens()

def _on_maas_peer_changed(self, event: ops.RelationEvent) -> None:
logger.info(event)
self.set_peer_data(self.unit, "system-name", socket.getfqdn())
Expand Down
45 changes: 44 additions & 1 deletion maas-region-charm/tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@

import ops
import ops.testing
from charm import MAAS_DB_NAME, MAAS_HTTP_PORT, MAAS_PEER_NAME, MAAS_SNAP_CHANNEL, MaasRegionCharm
import yaml
from charm import (
MAAS_API_RELATION,
MAAS_DB_NAME,
MAAS_HTTP_PORT,
MAAS_PEER_NAME,
MAAS_PROXY_PORT,
MAAS_SNAP_CHANNEL,
MaasRegionCharm,
)
from charms.maas_region_charm.v0 import maas


Expand Down Expand Up @@ -95,6 +104,21 @@ def test_peer_relation_data(self):
self.harness.charm.set_peer_data(self.harness.charm.app, "test_key", None)
self.assertEqual(self.harness.get_relation_data(rel_id, app_name)["test_key"], "{}")

@patch("charm.MaasHelper", autospec=True)
def test_ha_proxy_data(self, mock_helper):
self.harness.set_leader(True)
self.harness.begin()
ha = self.harness.add_relation(
MAAS_API_RELATION, "haproxy", unit_data={"public-address": "proxy.maas"}
)

ha_data = yaml.safe_load(self.harness.get_relation_data(ha, "maas-region/0")["services"])
self.assertEqual(len(ha_data), 1)
self.assertIn("service_name", ha_data[0])
self.assertIn("service_host", ha_data[0])
self.assertEqual(len(ha_data[0]["servers"]), 1)
self.assertEqual(ha_data[0]["servers"][0][1], "10.0.0.10")

@patch("charm.MaasHelper", autospec=True)
def test_on_maas_cluster_changed_new_agent(self, mock_helper):
mock_helper.get_maas_mode.return_value = "region"
Expand All @@ -113,6 +137,25 @@ def test_on_maas_cluster_changed_new_agent(self, mock_helper):
self.assertEqual(data["regions"], f'["{socket.getfqdn()}"]')
self.assertIn("maas_secret_id", data)

@patch(
"charm.MaasRegionCharm.connection_string",
new_callable=PropertyMock(return_value="postgres://"),
)
@patch("charm.MaasHelper", autospec=True)
def test_ha_proxy_update_api_url(self, mock_helper, _mock_conn_id):
mock_helper.get_maas_mode.return_value = "region"
mock_helper.get_maas_secret.return_value = "very-secret"
self.harness.set_leader(True)
self.harness.begin()
self.harness.add_relation(
MAAS_API_RELATION, "haproxy", unit_data={"public-address": "proxy.maas"}
)
mock_helper.setup_region.assert_called_once_with(
f"http://proxy.maas:{MAAS_PROXY_PORT}/MAAS",
"postgres://",
"region",
)

@patch(
"charm.MaasRegionCharm.connection_string",
new_callable=PropertyMock(return_value="postgres://"),
Expand Down

0 comments on commit 7b41024

Please sign in to comment.