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

[TEST] setpriv backups #221

Closed
wants to merge 15 commits into from
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ jobs:
echo "mark_expression=" >> $GITHUB_OUTPUT
else
echo Skipping unstable tests
echo "mark_expression=not unstable" >> $GITHUB_OUTPUT
echo "mark_expression=and not unstable" >> $GITHUB_OUTPUT
fi
- name: Run integration tests
run: tox run -e ${{ matrix.tox-environments }}-${{ env.libjuju }} -- -m 'not not${{ env.libjuju }} and ${{ steps.select-tests.outputs.mark_expression }}' --keep-models
run: tox run -e ${{ matrix.tox-environments }}-${{ env.libjuju }} -- -m 'not not${{ env.libjuju }} ${{ steps.select-tests.outputs.mark_expression }}' --keep-models
env:
AWS_ACCESS_KEY: "${{ secrets.AWS_ACCESS_KEY }}"
AWS_SECRET_KEY: "${{ secrets.AWS_SECRET_KEY }}"
Expand Down
17 changes: 4 additions & 13 deletions src/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import json
import logging
import os
import pwd
import re
import shutil
import tempfile
Expand Down Expand Up @@ -242,22 +241,11 @@ def _execute_command(
timeout: int = None,
) -> Tuple[int, str, str]:
"""Execute a command in the workload container."""

def demote():
pw_record = pwd.getpwnam("snap_daemon")

def result():
os.setgid(pw_record.pw_gid)
os.setuid(pw_record.pw_uid)

return result

process = run(
command,
input=command_input,
stdout=PIPE,
stderr=PIPE,
preexec_fn=demote(),
timeout=timeout,
)
return process.returncode, process.stdout.decode(), process.stderr.decode()
Expand Down Expand Up @@ -411,6 +399,9 @@ def _initialise_stanza(self) -> None:

@property
def _is_primary_pgbackrest_service_running(self) -> bool:
if not self.charm.primary_endpoint:
logger.warning("Failed to contact pgBackRest TLS server: no promary endpoint")
return False
return_code, _, stderr = self._execute_command(
[PGBACKREST_EXECUTABLE, "server-ping", "--io-timeout=10", self.charm.primary_endpoint]
)
Expand Down Expand Up @@ -654,7 +645,7 @@ def _on_restore_action(self, event):
logger.info("Removing previous cluster information")
return_code, _, stderr = self._execute_command(
[
"charmed-postgresql.patronictl",
"charmed-postgresql.constrained-patronictl",
"-c",
f"{PATRONI_CONF_PATH}/patroni.yaml",
"remove",
Expand Down
21 changes: 4 additions & 17 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,9 @@ def _on_peer_relation_changed(self, event: HookEvent):
event.defer()
return

if "exporter-started" not in self.unit_peer_data:
self._setup_exporter()

self._update_new_unit_status()

def _update_new_unit_status(self) -> None:
Expand Down Expand Up @@ -822,15 +825,6 @@ def _on_install(self, event: InstallEvent) -> None:
postgres_snap.alias("patronictl")
postgres_snap.alias("psql")

# Create the user home directory for the snap_daemon user.
# This is needed due to https://bugs.launchpad.net/snapd/+bug/2011581.
try:
subprocess.check_call("mkdir -p /home/snap_daemon".split())
subprocess.check_call("chown snap_daemon:snap_daemon /home/snap_daemon".split())
subprocess.check_call("usermod -d /home/snap_daemon snap_daemon".split())
except subprocess.CalledProcessError:
logger.exception("Unable to create snap_daemon home dir")

self.unit.status = WaitingStatus("waiting to start PostgreSQL")

def _on_leader_elected(self, event: LeaderElectedEvent) -> None:
Expand Down Expand Up @@ -956,14 +950,6 @@ def _on_start(self, event: StartEvent) -> None:

self.unit.set_workload_version(self._patroni.get_postgresql_version())

try:
# Set up the postgresql_exporter options.
self._setup_exporter()
except snap.SnapError:
logger.error("failed to set up postgresql_exporter options")
self.unit.status = BlockedStatus("failed to set up postgresql_exporter options")
return

# Open port
try:
self.unit.open_port("tcp", 5432)
Expand Down Expand Up @@ -994,6 +980,7 @@ def _setup_exporter(self) -> None:
postgres_snap.start(services=[MONITORING_SNAP_SERVICE], enable=True)
else:
postgres_snap.restart(services=[MONITORING_SNAP_SERVICE])
self.unit_peer_data.update({"exporter-started": "True"})

def _start_primary(self, event: StartEvent) -> None:
"""Bootstrap the cluster."""
Expand Down
4 changes: 2 additions & 2 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
SYSTEM_USERS = [BACKUP_USER, REPLICATION_USER, REWIND_USER, USER, MONITORING_USER]

# Snap constants.
PGBACKREST_EXECUTABLE = "charmed-postgresql.pgbackrest"
PGBACKREST_EXECUTABLE = "charmed-postgresql.constrained-pgbackrest"
POSTGRESQL_SNAP_NAME = "charmed-postgresql"
SNAP_PACKAGES = [(POSTGRESQL_SNAP_NAME, {"revision": "70"})]
SNAP_PACKAGES = [(POSTGRESQL_SNAP_NAME, {"revision": "76"})]

SNAP_COMMON_PATH = "/var/snap/charmed-postgresql/common"
SNAP_CURRENT_PATH = "/var/snap/charmed-postgresql/current"
Expand Down
3 changes: 1 addition & 2 deletions src/grafana_dashboards/postgresql-metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -3034,7 +3034,6 @@
"tags": [
"postgres",
"db",
"stats"
],
"templating": {
"list": [
Expand Down Expand Up @@ -3263,7 +3262,7 @@
]
},
"timezone": "",
"title": "PostgreSQL Database",
"title": "PostgreSQL",
"uid": "000000039",
"version": 1
}
25 changes: 13 additions & 12 deletions tests/unit/test_backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import PosixPath
from subprocess import PIPE, CompletedProcess, TimeoutExpired
from typing import OrderedDict
from unittest.mock import ANY, MagicMock, PropertyMock, call, mock_open, patch
from unittest.mock import MagicMock, PropertyMock, call, mock_open, patch

import botocore as botocore
from boto3.exceptions import S3UploadFailedError
Expand Down Expand Up @@ -392,31 +392,25 @@ def test_change_connectivity_to_database(self, _update_config):
)
_update_config.assert_called_once()

@patch("pwd.getpwnam")
@patch("backups.run")
def test_execute_command(self, _run, _getpwnam):
def test_execute_command(self, _run):
# Test when the command fails.
command = "rm -r /var/lib/postgresql/data/pgdata".split()
_run.return_value = CompletedProcess(command, 1, b"", b"fake stderr")
self.assertEqual(self.charm.backup._execute_command(command), (1, "", "fake stderr"))
_run.assert_called_once_with(
command, input=None, stdout=PIPE, stderr=PIPE, preexec_fn=ANY, timeout=None
)
_getpwnam.assert_called_once_with("snap_daemon")
_run.assert_called_once_with(command, input=None, stdout=PIPE, stderr=PIPE, timeout=None)

# Test when the command runs successfully.
_run.reset_mock()
_getpwnam.reset_mock()
_run.side_effect = None
_run.return_value = CompletedProcess(command, 0, b"fake stdout", b"")
self.assertEqual(
self.charm.backup._execute_command(command, command_input=b"fake input", timeout=5),
(0, "fake stdout", ""),
)
_run.assert_called_once_with(
command, input=b"fake input", stdout=PIPE, stderr=PIPE, preexec_fn=ANY, timeout=5
command, input=b"fake input", stdout=PIPE, stderr=PIPE, timeout=5
)
_getpwnam.assert_called_once_with("snap_daemon")

def test_format_backup_list(self):
# Test when there are no backups.
Expand Down Expand Up @@ -521,7 +515,7 @@ def test_initialise_stanza(
# Test when the blocked state is any of the blocked stated that can be solved
# by new S3 settings, but the stanza creation fails.
stanza_creation_command = [
"charmed-postgresql.pgbackrest",
"charmed-postgresql.constrained-pgbackrest",
"--config=/var/snap/charmed-postgresql/current/etc/pgbackrest/pgbackrest.conf",
f"--stanza={self.charm.backup.stanza_name}",
"stanza-create",
Expand Down Expand Up @@ -595,9 +589,16 @@ def test_is_primary_pgbackrest_service_running(
self.assertFalse(self.charm.backup._is_primary_pgbackrest_service_running)
_execute_command.assert_called_once()

# Test when the endpoint is not generated.
_execute_command.reset_mock()
_primary_endpoint.return_value = None
self.assertFalse(self.charm.backup._is_primary_pgbackrest_service_running)
_execute_command.assert_not_called()

# Test when the pgBackRest succeeds on contacting the primary server.
_execute_command.reset_mock()
_execute_command.return_value = (0, "fake stdout", "")
_primary_endpoint.return_value = "fake_endpoint"
self.assertTrue(self.charm.backup._is_primary_pgbackrest_service_running)
_execute_command.assert_called_once()

Expand Down Expand Up @@ -995,7 +996,7 @@ def test_on_restore_action(
)
_execute_command.assert_called_once_with(
[
"charmed-postgresql.patronictl",
"charmed-postgresql.constrained-patronictl",
"-c",
"/var/snap/charmed-postgresql/current/etc/patroni/patroni.yaml",
"remove",
Expand Down
45 changes: 2 additions & 43 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,49 +72,6 @@ def test_on_install(
pg_snap.alias.assert_any_call("psql")
pg_snap.alias.assert_any_call("patronictl")

assert _check_call.call_count == 3
_check_call.assert_any_call("mkdir -p /home/snap_daemon".split())
_check_call.assert_any_call("chown snap_daemon:snap_daemon /home/snap_daemon".split())
_check_call.assert_any_call("usermod -d /home/snap_daemon snap_daemon".split())

# Assert the status set by the event handler.
self.assertTrue(isinstance(self.harness.model.unit.status, WaitingStatus))

@patch_network_get(private_address="1.1.1.1")
@patch("charm.logger.exception")
@patch("charm.subprocess.check_call")
@patch("charm.snap.SnapCache")
@patch("charm.PostgresqlOperatorCharm._install_snap_packages")
@patch("charm.PostgresqlOperatorCharm._reboot_on_detached_storage")
@patch(
"charm.PostgresqlOperatorCharm._is_storage_attached",
side_effect=[False, True, True],
)
def test_on_install_failed_to_create_home(
self,
_is_storage_attached,
_reboot_on_detached_storage,
_install_snap_packages,
_snap_cache,
_check_call,
_logger_exception,
):
# Test without storage.
self.charm.on.install.emit()
_reboot_on_detached_storage.assert_called_once()
pg_snap = _snap_cache.return_value[POSTGRESQL_SNAP_NAME]
_check_call.side_effect = [subprocess.CalledProcessError(-1, ["test"])]

# Test without adding Patroni resource.
self.charm.on.install.emit()
# Assert that the needed calls were made.
_install_snap_packages.assert_called_once_with(packages=SNAP_PACKAGES)
assert pg_snap.alias.call_count == 2
pg_snap.alias.assert_any_call("psql")
pg_snap.alias.assert_any_call("patronictl")

_logger_exception.assert_called_once_with("Unable to create snap_daemon home dir")

# Assert the status set by the event handler.
self.assertTrue(isinstance(self.harness.model.unit.status, WaitingStatus))

Expand Down Expand Up @@ -1171,6 +1128,7 @@ def test_on_cluster_topology_change_clear_blocked(
self.assertTrue(isinstance(self.harness.model.unit.status, ActiveStatus))

@patch_network_get(private_address="1.1.1.1")
@patch("charm.snap.SnapCache")
@patch("charm.PostgresqlOperatorCharm._update_relation_endpoints")
@patch("charm.PostgresqlOperatorCharm.primary_endpoint", new_callable=PropertyMock)
@patch("charm.Patroni.member_started", new_callable=PropertyMock)
Expand All @@ -1187,6 +1145,7 @@ def test_on_peer_relation_changed(
_member_started,
_primary_endpoint,
_update_relation_endpoints,
_,
):
# Test an uninitialized cluster.
mock_event = Mock()
Expand Down