Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DPE-3049] templated config #186

Merged
merged 61 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
fcd5ba5
Generate templated cfg side by side
dragomirp Jan 23, 2024
fc39f5e
Merge branch 'main' into dpe-3049-templated-config
dragomirp Jan 23, 2024
2841636
Templated config WIP
dragomirp Jan 23, 2024
ca23974
Disable unit tests
dragomirp Jan 23, 2024
c263581
Legacy templating WIP
dragomirp Jan 23, 2024
bf52697
Fix TLS
dragomirp Jan 23, 2024
a58f334
Legacy admins
dragomirp Jan 23, 2024
496363b
Cleanup
dragomirp Jan 23, 2024
c658f3b
Remove update method
dragomirp Jan 24, 2024
4591d3e
Cleanup
dragomirp Jan 24, 2024
4fbbeea
Merge branch 'main' into dpe-3049-templated-config
dragomirp Jan 24, 2024
d501205
Linting
dragomirp Jan 24, 2024
34fdbd7
Restart on peer change
dragomirp Jan 24, 2024
c3c8f13
Full restart on peer change
dragomirp Jan 24, 2024
f0f945b
Revert "Full restart on peer change"
dragomirp Jan 24, 2024
8f0a651
Avoid early finalising of the new interface
dragomirp Jan 24, 2024
2118cd8
Try to suppress non leader warnings
dragomirp Jan 25, 2024
ae3e9a1
Fix port change
dragomirp Jan 25, 2024
6cb7a60
Redundant rerener
dragomirp Jan 25, 2024
d82626e
Merge branch 'main' into dpe-3049-templated-config
dragomirp Jan 29, 2024
a11417c
Merge branch 'main' into dpe-3049-templated-config
dragomirp Jan 29, 2024
53951bb
Merge branch 'main' into dpe-3049-templated-config
dragomirp Feb 5, 2024
9cff0f2
Unify check_backend
dragomirp Feb 6, 2024
5e6e8a4
Disable constant defers
dragomirp Feb 6, 2024
148e6af
Merge branch 'main' into dpe-3049-templated-config
dragomirp Feb 6, 2024
1821b03
Defer instead of ignore
dragomirp Feb 6, 2024
98e57bd
Don't wait for retries
dragomirp Feb 6, 2024
a974c09
Filter database
dragomirp Feb 6, 2024
f852a19
[charm] Update charm dependencies
renovate[bot] Feb 7, 2024
66698e3
Remove flag
dragomirp Feb 7, 2024
6427c8f
Reset flag
dragomirp Feb 7, 2024
12cc43a
Defer _on_change
dragomirp Feb 7, 2024
3fff3ba
Filter out new relation
dragomirp Feb 7, 2024
a397564
Put mapping in peer data
dragomirp Feb 8, 2024
79606f9
Str rel ids
dragomirp Feb 8, 2024
1cf18eb
Update read only endpoints on peer drift
dragomirp Feb 8, 2024
0220e21
Merge branch 'main' into dpe-3049-templated-config
dragomirp Feb 8, 2024
27b9698
Only one db per rel
dragomirp Feb 8, 2024
8a6da31
Missed dynamic params
dragomirp Feb 8, 2024
d7b5107
Suppress log error
dragomirp Feb 8, 2024
711aa52
Restore auth file on pebble ready
dragomirp Feb 8, 2024
ff8c7ea
Don't read auth file
dragomirp Feb 8, 2024
3a369a5
Removing PgbConfig
dragomirp Feb 9, 2024
500a33b
Reenable only working tests
dragomirp Feb 9, 2024
a2d7548
Add tests WIP
dragomirp Feb 9, 2024
826c597
Merge remote-tracking branch 'origin/renovate/charm-dependencies' int…
dragomirp Feb 9, 2024
3f33744
Don't render the dict
dragomirp Feb 9, 2024
2a80cda
Upgrade tweaks
dragomirp Feb 9, 2024
57c6d4c
Restoring unit tests
dragomirp Feb 10, 2024
19b13e1
Try not to rerender on legacy rel change
dragomirp Feb 10, 2024
b40fcd1
Restore rerender on legacy rel change
dragomirp Feb 10, 2024
be16f35
Restoring tests
dragomirp Feb 10, 2024
6928cf3
Container check in _on_config_change
dragomirp Feb 10, 2024
6d05a31
Dead code
dragomirp Feb 10, 2024
dad32fd
Unit tests
dragomirp Feb 12, 2024
e28fd8c
Merge branch 'main' into dpe-3049-templated-config
dragomirp Feb 13, 2024
3620aa4
Bump coveage
dragomirp Feb 13, 2024
4426e8f
Don't raise on blocked
dragomirp Feb 14, 2024
b5f1623
Merge branch 'main' into dpe-3049-templated-config
dragomirp Feb 14, 2024
dac81ad
Switch status
dragomirp Feb 14, 2024
3a2dbbc
Don't update libjuju
dragomirp Feb 14, 2024
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
385 changes: 2 additions & 383 deletions lib/charms/pgbouncer_k8s/v0/pgb.py
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to salvage anything else from here?

Large diffs are not rendered by default.

284 changes: 144 additions & 140 deletions poetry.lock

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ repository = "https://github.com/canonical/pgbouncer-k8s-operator"

[tool.poetry.dependencies]
python = "^3.8.10"
ops = "^2.9.0"
cryptography = "^42.0.1"
ops = "^2.10.0"
cryptography = "^42.0.2"
jsonschema = "^4.21.1"
tenacity = "^8.2.3"
cosl = "^0.0.7"
poetry-core = "^1.8.1"
lightkube = "^0.15.0"
cosl = "^0.0.8"
poetry-core = "^1.9.0"
lightkube = "^0.15.1"
lightkube-models = "^1.29.0.6"
pydantic = "^1.10.14"
psycopg2 = "^2.9.9"
psycopg = {extras = ["c"], version = "^3.1.17"}
psycopg = {extras = ["c"], version = "^3.1.18"}

[tool.poetry.group.charm-libs.dependencies]
# data_platform_libs/v0/data_interfaces.py
Expand Down
359 changes: 180 additions & 179 deletions src/charm.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
PGB_LOG_DIR = "/var/log/pgbouncer"
MONITORING_PASSWORD_KEY = "monitoring_password"
AUTH_FILE_DATABAG_KEY = "auth_file"
CFG_FILE_DATABAG_KEY = "cfg_file"

EXTENSIONS_BLOCKING_MESSAGE = "bad relation request - remote app requested extensions, which are unsupported. Please remove this relation."
CONTAINER_UNAVAILABLE_MESSAGE = "PgBouncer container currently unavailable"

SECRET_LABEL = "secret"
SECRET_INTERNAL_LABEL = "internal-secret"
Expand All @@ -36,7 +38,6 @@
UNIT_SCOPE = "unit"

SECRET_KEY_OVERRIDES = {
"cfg_file": "cfg-file",
"ca": "cauth",
"monitoring_password": "monitoring-password",
"auth_file": "auth-file",
Expand Down
110 changes: 60 additions & 50 deletions src/relations/backend_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"""

import logging
from typing import Dict, List, Set
from typing import Dict, List, Optional, Set

import psycopg2
from charms.data_platform_libs.v0.data_interfaces import (
Expand All @@ -55,13 +55,13 @@
BlockedStatus,
MaintenanceStatus,
Relation,
WaitingStatus,
)
from ops.pebble import ConnectionError
from ops.pebble import ConnectionError, PathError

from constants import (
APP_SCOPE,
AUTH_FILE_DATABAG_KEY,
AUTH_FILE_PATH,
BACKEND_RELATION_NAME,
MONITORING_PASSWORD_KEY,
PG,
Expand Down Expand Up @@ -148,7 +148,7 @@ def postgres(self) -> PostgreSQL:
)

@property
def auth_user(self):
def auth_user(self) -> Optional[str]:
"""Username for auth_user."""
if not self.relation:
return None
Expand All @@ -159,10 +159,19 @@ def auth_user(self):
return f"pgbouncer_auth_{username}".replace("-", "_")

@property
def stats_user(self):
def stats_user(self) -> str:
"""Username for stats."""
if not self.relation:
return ""
return f"pgbouncer_stats_{self.charm.app.name}".replace("-", "_")

@property
def auth_query(self) -> str:
"""Generate auth query."""
if not self.relation:
return ""
return f"SELECT username, password FROM {self.auth_user}.get_auth($1)"

@property
def postgres_databag(self) -> Dict:
"""Wrapper around accessing the remote application databag for the backend relation.
Expand All @@ -187,22 +196,11 @@ def ready(self) -> bool:
"""
# Check we have connection information
if not self.postgres:
logger.debug("Backend not ready: no connection info")
return False

try:
cfg = self.charm.read_pgb_config()
except FileNotFoundError:
# Not ready, no config
return False

# Check we can authenticate
if "auth_query" not in cfg["pgbouncer"].keys():
# Not ready, backend relation not initialised
return False
try:
cfg = self.charm.read_auth_file()
except FileNotFoundError:
# Not ready, no auth file to authenticate our pgb user
if not self.charm.get_secret(APP_SCOPE, AUTH_FILE_DATABAG_KEY):
logger.debug("Backend not ready: no auth file secret set")
return False

# Check we can actually connect to backend database by running a command.
Expand All @@ -212,7 +210,7 @@ def ready(self) -> bool:
cursor.execute("SELECT version();")
conn.close()
except (psycopg2.Error, psycopg2.OperationalError):
logger.error("PostgreSQL connection failed")
logger.warning("PostgreSQL connection failed")
return False

return True
Expand All @@ -223,6 +221,18 @@ def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
Accesses user and password generated by the postgres charm and adds a user.
"""
if not self.charm.unit.is_leader():
# Pebble ready will set the config
if not self.charm.is_container_ready:
return

if not (auth_file := self.charm.get_secret(APP_SCOPE, AUTH_FILE_DATABAG_KEY)):
logger.debug("_on_database_created deferred: waiting for leader to initialise")
event.defer()
return
self.charm.render_auth_file(auth_file)
self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.toggle_monitoring_layer(True)
self.charm.unit.status = ActiveStatus()
return

logger.info("initialising pgbouncer backend relation")
Expand Down Expand Up @@ -261,21 +271,13 @@ def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
self.charm.set_secret(APP_SCOPE, AUTH_FILE_DATABAG_KEY, auth_file)
self.charm.render_auth_file(auth_file)

cfg = self.charm.read_pgb_config()
cfg.add_user(user=self.stats_user, stats=True)
cfg["pgbouncer"][
"auth_query"
] = f"SELECT username, password FROM {self.auth_user}.get_auth($1)"
cfg["pgbouncer"]["auth_file"] = AUTH_FILE_PATH
self.charm.render_pgb_config(cfg)

self.charm.update_postgres_endpoints(reload_pgbouncer=True)
self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.toggle_monitoring_layer(True)

self.charm.unit.status = ActiveStatus("backend-database relation initialised.")

def _on_endpoints_changed(self, _):
self.charm.update_postgres_endpoints(reload_pgbouncer=True)
self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.update_client_connection_info()

def _on_relation_changed(self, _):
Expand All @@ -288,7 +290,7 @@ def _on_relation_changed(self, _):
logger.debug("_on_reltion_changed early exit: pebble ready not fired")
return

self.charm.update_postgres_endpoints(reload_pgbouncer=True)
self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.update_client_connection_info()

def _on_relation_departed(self, event: RelationDepartedEvent):
Expand All @@ -298,15 +300,15 @@ def _on_relation_departed(self, event: RelationDepartedEvent):
the postgres relation-broken hook removes the user needed to remove authentication for the
users we create.
"""
self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.update_client_connection_info()
self.charm.update_postgres_endpoints(reload_pgbouncer=True)

if event.departing_unit == self.charm.unit:
# This should only occur when the relation is being removed, not on scale-down
self.charm.peers.unit_databag.update(
{f"{BACKEND_RELATION_NAME}_{event.relation.id}_departing": "true"}
)
logger.error("added relation-departing flag to peer databag")
logger.warning("added relation-departing flag to peer databag")
return

if not self.charm.unit.is_leader() or event.departing_unit.app != self.charm.app:
Expand Down Expand Up @@ -343,27 +345,21 @@ def _on_relation_broken(self, event: RelationBrokenEvent):
"""
depart_flag = f"{BACKEND_RELATION_NAME}_{event.relation.id}_departing"
self.charm.toggle_monitoring_layer(False)
if (
self.charm.peers.unit_databag.get(depart_flag, False)
or not self.charm.unit.is_leader()
):
if self.charm.peers.unit_databag.get(depart_flag, False):
logging.info("exiting relation-broken hook - nothing to do")
return

try:
cfg = self.charm.read_pgb_config()
except FileNotFoundError:
event.defer()
return

cfg.remove_user(self.postgres.user)
cfg["pgbouncer"].pop("auth_user", None)
cfg["pgbouncer"].pop("auth_query", None)
cfg["pgbouncer"].pop("auth_file", None)
self.charm.render_pgb_config(cfg)

self.charm.delete_file(f"{PGB_DIR}/userlist.txt")
self.charm.peers.update_auth_file(auth_file=None)
self.charm.delete_file(f"{PGB_DIR}/userlist.txt")
except PathError:
logger.warning("Cannot delete userlist.txt")
if self.charm.unit.is_leader():
self.charm.remove_secret(APP_SCOPE, AUTH_FILE_DATABAG_KEY)

self.charm.render_pgb_config(reload_pgbouncer=True)
self.charm.unit.status = BlockedStatus(
"waiting for backend database relation to initialise"
)

def initialise_auth_function(self, dbs: List[str]):
"""Runs an SQL script to initialise the auth function.
Expand Down Expand Up @@ -412,3 +408,17 @@ def get_read_only_endpoints(self) -> Set[str]:
if not read_only_endpoints:
return set()
return set(read_only_endpoints.split(","))

def check_backend(self) -> bool:
"""Verifies backend is ready and updates status.

Returns:
bool signifying whether backend is ready or not
"""
if not self.ready:
# We can't relate an app to the backend database without a backend postgres relation
wait_str = "waiting for backend-database relation to connect"
logger.warning(wait_str)
self.charm.unit.status = WaitingStatus(wait_str)
return False
return True
Loading
Loading