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
20 changes: 9 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pysyncobj = "^0.3.14"
psutil = "^7.1.0"
charm-refresh = "^3.1.0.2"
httpx = "^0.28.1"
postgresql-charms-single-kernel = "^0.0.1"
postgresql-charms-single-kernel = "16.0.2"

[tool.poetry.group.charm-libs.dependencies]
# data_platform_libs/v0/data_interfaces.py
Expand Down
3 changes: 2 additions & 1 deletion src/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from requests.auth import HTTPBasicAuth
from single_kernel_postgresql.config.literals import (
PEER,
POSTGRESQL_STORAGE_PERMISSIONS,
REWIND_USER,
USER,
)
Expand Down Expand Up @@ -229,7 +230,7 @@ def configure_patroni_on_unit(self):

# Expected permission
# Replicas refuse to start with the default permissions
os.chmod(POSTGRESQL_DATA_PATH, 0o750) # noqa: S103
os.chmod(POSTGRESQL_DATA_PATH, POSTGRESQL_STORAGE_PERMISSIONS)

def _change_owner(self, path: str) -> None:
"""Change the ownership of a file or a directory to the postgres user.
Expand Down
100 changes: 100 additions & 0 deletions tests/integration/test_tmpfs_restart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
# Copyright 2025 Canonical Ltd.
# See LICENSE file for licensing details.

import logging
import subprocess

import jubilant
import psycopg2
import pytest

from .helpers import DATABASE_APP_NAME
from .jubilant_helpers import get_credentials

logger = logging.getLogger(__name__)

TIMEOUT = 20 * 60
RELATION_ENDPOINT = "postgresql"
DATA_INTEGRATOR_APP_NAME = "data-integrator"


@pytest.mark.abort_on_fail
def test_deploy_with_tmpfs_storage(juju: jubilant.Juju, charm) -> None:
"""Deploy PostgreSQL with tmpfs temp storage and data-integrator."""
# Deploy database app with tmpfs for temporary storage.
if DATABASE_APP_NAME not in juju.status().apps:
logger.info("Deploying PostgreSQL with tmpfs temporary storage")
juju.deploy(
charm,
app=DATABASE_APP_NAME,
num_units=1,
config={"profile": "testing"},
storage={"temp": "5G,tmpfs"},
)

# Deploy data-integrator to get credentials.
if DATA_INTEGRATOR_APP_NAME not in juju.status().apps:
logger.info("Deploying data-integrator")
juju.deploy(DATA_INTEGRATOR_APP_NAME, config={"database-name": "test"})

# Relate if not already related
status = juju.status()
if not status.apps[DATABASE_APP_NAME].relations.get(RELATION_ENDPOINT):
juju.integrate(DATA_INTEGRATOR_APP_NAME, DATABASE_APP_NAME)

logger.info("Waiting for both applications to become active")
juju.wait(
lambda s: jubilant.all_active(s, DATABASE_APP_NAME, DATA_INTEGRATOR_APP_NAME),
timeout=TIMEOUT,
)


def test_restart_and_temp_table(juju: jubilant.Juju) -> None:
"""Restart the LXD machine and verify TEMP TABLE creation works afterwards."""
unit_name = f"{DATABASE_APP_NAME}/0"

# Find machine name and restart
status = juju.status()
unit_info = status.get_units(unit_name.split("/")[0]).get(unit_name)
machine_name = None
if unit_info:
machine_id = getattr(unit_info, "machine", None)
if machine_id:
# Look up the machine object in status and try common attributes that hold the LXD name
machine_obj = (
getattr(status, "machines", {}).get(machine_id)
if hasattr(status, "machines")
else None
)
if machine_obj:
machine_name = getattr(machine_obj, "instance_id", None)

if machine_name is None:
raise RuntimeError("Unable to determine LXD machine/container name for unit " + unit_name)

logger.info(f"Restarting LXD machine {machine_name}")
subprocess.check_call(["lxc", "restart", machine_name])

# Wait for unit to go active/idle again
logger.info("Waiting for PostgreSQL unit to become active after restart")
juju.wait(
lambda s: jubilant.all_active(s, DATABASE_APP_NAME, DATA_INTEGRATOR_APP_NAME),
delay=30,
timeout=TIMEOUT,
)

# Obtain credentials via data-integrator action
creds = get_credentials(juju, f"{DATA_INTEGRATOR_APP_NAME}/0")
uri = creds[RELATION_ENDPOINT]["uris"]

# Connect and create a TEMPORARY TABLE
connection = None
try:
connection = psycopg2.connect(uri)
connection.autocommit = True
with connection.cursor() as cur:
cur.execute("CREATE TEMPORARY TABLE test (lines TEXT);")
finally:
if connection is not None:
connection.close()
7 changes: 7 additions & 0 deletions tests/spread/test_tmpfs_restart.py/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
summary: test_tmpfs_restart.py
environment:
TEST_MODULE: test_tmpfs_restart.py
execute: |
tox run -e integration -- "tests/integration/$TEST_MODULE" --model testing --alluredir="$SPREAD_TASK/allure-results"
artifacts:
- allure-results
2 changes: 1 addition & 1 deletion tests/unit/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ def test_configure_patroni_on_unit(peers_ips, patroni):

_open.assert_called_once_with(CREATE_CLUSTER_CONF_PATH, "a")
_chmod.assert_called_once_with(
"/var/snap/charmed-postgresql/common/var/lib/postgresql", 488
"/var/snap/charmed-postgresql/common/var/lib/postgresql", 448
)


Expand Down