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-5833] Create new PostgreSQL version 16 charm #788

Open
wants to merge 19 commits into
base: 16/edge
Choose a base branch
from
Open
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ jobs:
build:
name: Build charm
uses: canonical/data-platform-workflows/.github/workflows/[email protected]
with:
cache: false

integration-test:
strategy:
fail-fast: false
matrix:
juju:
- agent: 2.9.51 # renovate: juju-agent-pin-minor
libjuju: ==2.9.49.1 # renovate: latest libjuju 2
allure_on_amd64: false
Copy link
Author

Choose a reason for hiding this comment

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

no more juju 2

- agent: 3.6.2 # renovate: juju-agent-pin-minor
libjuju: ==3.6.1.0 # renovate: latest libjuju 2
allure_on_amd64: true
architecture:
- amd64
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Release to Charmhub
on:
push:
branches:
- main
- 16/edge
paths-ignore:
- 'tests/**'
- 'docs/**'
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
- ci-tests
uses: canonical/data-platform-workflows/.github/workflows/[email protected]
with:
channel: 14/edge
channel: 16/edge
artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }}
secrets:
charmhub-token: ${{ secrets.CHARMHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ juju model-config logging-config="<root>=INFO;unit=DEBUG"
microk8s enable rbac

# Deploy the charm
juju deploy ./postgresql-k8s_ubuntu-22.04-amd64.charm --trust \
juju deploy ./postgresql-k8s_ubuntu-24.04-amd64.charm --trust \
--resource postgresql-image=$(yq '(.resources.postgresql-image.upstream-source)' metadata.yaml)
```

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Bootstrap a Kubernetes (e.g. [Multipass-based MicroK8s](https://discourse.charmh

```shell
juju add-model postgresql-k8s
juju deploy postgresql-k8s --channel 14 --trust
juju deploy postgresql-k8s --channel 16/edge --trust
```

**Note:** the `--trust` flag is required because the charm and Patroni need to create some K8s resources.
Expand Down Expand Up @@ -62,7 +62,7 @@ Adding a relation is accomplished with `juju relate` (or `juju integrate` for Ju

```shell
# Deploy Charmed PostgreSQL cluster with 3 nodes
juju deploy postgresql-k8s -n 3 --trust --channel 14
juju deploy postgresql-k8s -n 3 --trust --channel 16/edge

# Deploy the relevant application charms
juju deploy mycharm
Expand All @@ -87,7 +87,7 @@ juju status --relations
This charm supports legacy interface `pgsql` from the previous [PostgreSQL charm](https://launchpad.net/postgresql-charm):

```shell
juju deploy postgresql-k8s --trust --channel 14
juju deploy postgresql-k8s --trust --channel 16/edge
juju deploy finos-waltz-k8s --channel edge
juju relate postgresql-k8s:db finos-waltz-k8s
```
Expand Down
4 changes: 2 additions & 2 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

type: charm
platforms:
ubuntu@22.04:amd64:
ubuntu@22.04:arm64:
ubuntu@24.04:amd64:
ubuntu@24.04:arm64:
# Files implicitly created by charmcraft without a part:
# - dispatch (https://github.com/canonical/charmcraft/pull/1898)
# - manifest.yaml
Expand Down
19 changes: 12 additions & 7 deletions lib/charms/postgresql_k8s/v0/postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 42
LIBPATCH = 43

INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles"

Expand Down Expand Up @@ -317,7 +317,7 @@ def delete_user(self, user: str) -> None:
logger.error(f"Failed to delete user: {e}")
raise PostgreSQLDeleteUserError() from e

def enable_disable_extensions(
def enable_disable_extensions( # noqa: C901
self, extensions: Dict[str, bool], database: Optional[str] = None
) -> None:
"""Enables or disables a PostgreSQL extension.
Expand Down Expand Up @@ -347,10 +347,12 @@ def enable_disable_extensions(

# Enable/disabled the extension in each database.
for database in databases:
with self._connect_to_database(
database=database
) as connection, connection.cursor() as cursor:
connection = self._connect_to_database(database=database)
connection.autocommit = True
with connection.cursor() as cursor:
for extension, enable in ordered_extensions.items():
if extension == "postgis":
cursor.execute("SET pgaudit.log = 'none';")
Copy link
Author

Choose a reason for hiding this comment

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

necessary for postgis to work

Copy link
Member

Choose a reason for hiding this comment

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

Interesting. So, postgis and pgaudit don't work together?

cursor.execute(
f"CREATE EXTENSION IF NOT EXISTS {extension};"
if enable
Expand All @@ -372,6 +374,7 @@ def _generate_database_privileges_statements(
) -> List[Composed]:
"""Generates a list of databases privileges statements."""
statements = []
statements.append(SQL("GRANT USAGE, CREATE ON SCHEMA public TO PUBLIC;"))
Copy link
Author

Choose a reason for hiding this comment

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

Here and below are the specific workarounds mentioned in the doc for the new restrictive permissions added on PG 15/16.

if relations_accessing_this_database == 1:
statements.append(
SQL(
Expand Down Expand Up @@ -428,8 +431,10 @@ def _generate_database_privileges_statements(
SQL("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA {} TO {};").format(
schema, Identifier(user)
),
SQL("GRANT USAGE ON SCHEMA {} TO {};").format(schema, Identifier(user)),
SQL("GRANT CREATE ON SCHEMA {} TO {};").format(schema, Identifier(user)),
SQL("GRANT USAGE, CREATE ON SCHEMA {} TO {};").format(
schema, Identifier(user)
),
SQL("GRANT USAGE, CREATE ON SCHEMA {} TO admin;").format(schema),
Comment on lines +434 to +437
Copy link
Member

Choose a reason for hiding this comment

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

I checked the PostgreSQL 15 release notes and changed my mind about the approach here. Now, I think that it's important that we don't grant those permissions to users on the public schema to avoid CVE-2018-1058.

])
return statements

Expand Down
2 changes: 1 addition & 1 deletion metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ resources:
postgresql-image:
type: oci-image
description: OCI image for PostgreSQL
upstream-source: ghcr.io/canonical/charmed-postgresql@sha256:7e41d7f60e45ee2f5463aa9aafcd3c35121423423ee08c26a174b99ad0235b7e # renovate: oci-image tag: 14.15-22.04_edge
upstream-source: ghcr.io/canonical/charmed-postgresql:16.4-24.04_edge
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to keep the SHA256 digest here because it'll prevent us from publishing the charm with a newer and not yet tested version of the OCI image.


peers:
database-peers:
Expand Down
16 changes: 14 additions & 2 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,12 @@ def update_config(self, is_creating_backup: bool = False) -> bool:
return True

if not self._patroni.member_started:
if self.is_tls_enabled:
logger.debug(
"Early exit update_config: patroni not responding but TLS is enabled."
)
self._handle_postgresql_restart_need()
return True
logger.debug("Early exit update_config: Patroni not started yet")
return False

Expand Down Expand Up @@ -1949,8 +1955,14 @@ def _validate_config_options(self) -> None:

def _handle_postgresql_restart_need(self):
"""Handle PostgreSQL restart need based on the TLS configuration and configuration changes."""
restart_postgresql = self.is_tls_enabled != self.postgresql.is_tls_enabled()
self._patroni.reload_patroni_configuration()
if self._can_connect_to_postgresql:
restart_postgresql = self.is_tls_enabled != self.postgresql.is_tls_enabled()
else:
restart_postgresql = False
try:
self._patroni.reload_patroni_configuration()
except Exception as e:
logger.error(f"Reload patroni call failed! error: {e!s}")
# Wait for some more time than the Patroni's loop_wait default value (10 seconds),
# which tells how much time Patroni will wait before checking the configuration
# file again to reload it.
Expand Down
4 changes: 2 additions & 2 deletions src/dependency.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"rock": {
"dependencies": {},
"name": "charmed-postgresql",
"upgrade_supported": "^14",
"version": "14.11"
"upgrade_supported": "^16",
"version": "16.6"
}
}
2 changes: 1 addition & 1 deletion src/patroni.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ def render_patroni_yml_file(
)
self._render_file(f"{self._storage_path}/patroni.yml", rendered, 0o644)

@retry(stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=2, max=30))
@retry(stop=stop_after_attempt(20), wait=wait_exponential(multiplier=1, min=2, max=30))
Copy link
Member

Choose a reason for hiding this comment

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

Did you have a link to a failing test that motivated this change? Did it happen multiple times?

def reload_patroni_configuration(self) -> None:
"""Reloads the configuration after it was updated in the file."""
requests.post(
Expand Down
15 changes: 15 additions & 0 deletions templates/patroni.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,26 @@ postgresql:
authentication:
replication:
password: {{ replication_password }}
{%- if enable_tls %}
sslrootcert: {{ conf_path }}/ca.pem
sslcert: {{ conf_path }}/cert.pem
sslkey: {{ conf_path }}/key.pem
{%- endif %}
Copy link
Author

Choose a reason for hiding this comment

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

no idea why this wasn't an issue before.

rewind:
username: {{ rewind_user }}
password: {{ rewind_password }}
{%- if enable_tls %}
sslrootcert: {{ conf_path }}/ca.pem
sslcert: {{ conf_path }}/cert.pem
sslkey: {{ conf_path }}/key.pem
{%- endif %}
superuser:
password: {{ superuser_password }}
{%- if enable_tls %}
sslrootcert: {{ conf_path }}/ca.pem
sslcert: {{ conf_path }}/cert.pem
sslkey: {{ conf_path }}/key.pem
{%- endif %}
use_endpoints: true
use_unix_socket: true
{%- if is_no_sync_member or is_creating_backup %}
Expand Down
4 changes: 2 additions & 2 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ variable "app_name" {
variable "channel" {
description = "Charm channel to use when deploying"
type = string
default = "14/stable"
default = "16/stable"
}

variable "revision" {
Expand All @@ -24,7 +24,7 @@ variable "revision" {
variable "base" {
description = "Application base"
type = string
default = "ubuntu@22.04"
default = "ubuntu@24.04"
}

variable "units" {
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import pytest
from pytest_operator.plugin import OpsTest

from .helpers import build_charm


@pytest.fixture(scope="module")
async def database_charm(ops_test: OpsTest):
"""Build the database charm."""
charm = await ops_test.build_charm(".")
charm = await build_charm(".")
Copy link
Author

Choose a reason for hiding this comment

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

workaround for multi-base integration testing (currently hardcoded to jammy)

return charm
9 changes: 2 additions & 7 deletions tests/integration/ha_tests/test_async_replication.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from .. import architecture, markers
from ..helpers import (
APPLICATION_NAME,
CHARM_BASE,
DATABASE_APP_NAME,
build_and_deploy,
get_leader_unit,
Expand Down Expand Up @@ -109,12 +108,8 @@ async def test_deploy_async_replication_setup(
"""Build and deploy two PostgreSQL cluster in two separate models to test async replication."""
await build_and_deploy(ops_test, CLUSTER_SIZE, wait_for_idle=False)
await build_and_deploy(ops_test, CLUSTER_SIZE, wait_for_idle=False, model=second_model)
await ops_test.model.deploy(
APPLICATION_NAME, channel="latest/edge", num_units=1, base=CHARM_BASE
)
await second_model.deploy(
APPLICATION_NAME, channel="latest/edge", num_units=1, base=CHARM_BASE
)
await ops_test.model.deploy(APPLICATION_NAME, channel="latest/edge", num_units=1)
await second_model.deploy(APPLICATION_NAME, channel="latest/edge", num_units=1)

async with ops_test.fast_forward(), fast_forward(second_model):
await gather(
Expand Down
2 changes: 0 additions & 2 deletions tests/integration/ha_tests/test_replication.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from ..helpers import (
APPLICATION_NAME,
CHARM_BASE,
app_name,
build_and_deploy,
db_connect,
Expand Down Expand Up @@ -43,7 +42,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None:
await ops_test.model.deploy(
APPLICATION_NAME,
application_name=APPLICATION_NAME,
base=CHARM_BASE,
Copy link
Author

Choose a reason for hiding this comment

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

this parameter still doesn't play nice with noble base.

channel="edge",
)

Expand Down
10 changes: 5 additions & 5 deletions tests/integration/ha_tests/test_rollback_to_master_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
from ..architecture import architecture
from ..helpers import (
APPLICATION_NAME,
CHARM_BASE,
DATABASE_APP_NAME,
METADATA,
build_charm,
get_leader_unit,
get_primary,
get_unit_by_index,
Expand All @@ -37,6 +37,7 @@


@pytest.mark.group(1)
@pytest.mark.unstable
@markers.juju3
@pytest.mark.unstable
@markers.amd64_only # TODO: remove after arm64 stable release
Expand All @@ -47,16 +48,14 @@ async def test_deploy_stable(ops_test: OpsTest) -> None:
ops_test.model.deploy(
DATABASE_APP_NAME,
num_units=3,
channel="14/stable",
channel="16/stable",
revision=LABEL_REVISION,
base=CHARM_BASE,
trust=True,
),
ops_test.model.deploy(
APPLICATION_NAME,
num_units=1,
channel="latest/edge",
base=CHARM_BASE,
),
)
logger.info("Wait for applications to become active")
Expand All @@ -72,6 +71,7 @@ async def test_deploy_stable(ops_test: OpsTest) -> None:


@pytest.mark.group(1)
@pytest.mark.unstable
@markers.juju3
@pytest.mark.unstable
@markers.amd64_only # TODO: remove after arm64 stable release
Expand All @@ -98,7 +98,7 @@ async def test_fail_and_rollback(ops_test, continuous_writes) -> None:
primary_name = await get_primary(ops_test, DATABASE_APP_NAME)
assert primary_name == f"{DATABASE_APP_NAME}/0"

local_charm = await ops_test.build_charm(".")
local_charm = await build_charm(".")
filename = local_charm.split("/")[-1] if isinstance(local_charm, str) else local_charm.name
fault_charm = Path("/tmp/", filename)
shutil.copy(local_charm, fault_charm)
Expand Down
2 changes: 0 additions & 2 deletions tests/integration/ha_tests/test_self_healing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from .. import markers
from ..helpers import (
APPLICATION_NAME,
CHARM_BASE,
METADATA,
app_name,
build_and_deploy,
Expand Down Expand Up @@ -69,7 +68,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None:
await ops_test.model.deploy(
APPLICATION_NAME,
application_name=APPLICATION_NAME,
base=CHARM_BASE,
channel="edge",
)

Expand Down
Loading
Loading