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-6326] pbm status fix #545

Open
wants to merge 5 commits into
base: 6/edge
Choose a base branch
from
Open
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
52 changes: 49 additions & 3 deletions lib/charms/mongodb/v1/mongodb_backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

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

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -224,7 +224,9 @@ def _on_create_backup_action(self, event) -> None:
MaintenanceStatus(f"backup started/running, backup id:'{backup_id}'")
)
self._success_action_with_info_log(
event, action, {"backup-status": f"backup started. backup id: {backup_id}"}
event,
action,
{"backup-status": f"backup started. backup id: {backup_id}"},
)
except (subprocess.CalledProcessError, ExecError, Exception) as e:
self._fail_action_with_error_log(event, action, str(e))
Expand Down Expand Up @@ -519,6 +521,13 @@ def get_pbm_status(self) -> Optional[StatusBase]:
if not self.model.get_relation(S3_RELATION):
logger.info("No configurations for backups, not relation to s3-charm.")
return None

if not self.are_s3_configurations_provided():
logger.info(
"relation to s3-charm exists, but not all necessary configurations have been set."
)
return BlockedStatus("s3 configurations missing.")

try:
previous_pbm_status = self.charm.unit.status
pbm_status = self.charm.run_pbm_command(PBM_STATUS_CMD)
Expand Down Expand Up @@ -570,7 +579,11 @@ def _generate_backup_list_output(self) -> str:
# pbm will occasionally report backups that are currently running as failed, so it is
# necessary to correct the backup list in this case.
if last_reported_backup[0] == running_backup["name"]:
backup_list[0] = (last_reported_backup[0], last_reported_backup[1], "in progress")
backup_list[0] = (
last_reported_backup[0],
last_reported_backup[1],
"in progress",
)
else:
backup_list.append((running_backup["name"], "logical", "in progress"))

Expand Down Expand Up @@ -806,3 +819,36 @@ def get_backup_error_status(self, backup_id: str) -> str:
return backup.get("error", "")

return ""

def are_s3_configurations_provided(self) -> bool:
"""Returns True if not all necessary configurations for s3 are provided.

Minimum necessary s3 credentials can be found by reading the PBM page:
https://docs.percona.com/percona-backup-mongodb/reference/configuration-options.html#s3-type-storage-options
"""
if not self.model.get_relation(S3_RELATION):
logger.info("No configurations for backups, not relation to s3-charm.")
return False

provided_configs = self._get_pbm_configs()
if (
not "storage.s3.access-key" not in provided_configs
or "storage.s3.secret-key" not in provided_configs
):
logger.info("Missing s3 credentials")
return False

# note this is more of a sanity check - the s3 lib defaults this to the relation name
if "storage.s3.bucket" not in provided_configs:
logger.info("Missing bucket")
return False

# since we cannot determine whether the user has an AWS or GCP bucket or Minio bucket
# send them an info
if "storage.s3.region" not in provided_configs:
logger.info("Missing region - this is required for AWS and GCP")

if "storage.s3.endpointUrl" not in provided_configs:
logger.info("Missing endpointUrl - this is required for MinIO and GCP")

return True
25 changes: 17 additions & 8 deletions tests/integration/backup_tests/test_backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None:
await ops_test.model.wait_for_idle(timeout=DEPLOYMENT_TIMEOUT)


@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
@pytest.mark.group(1)
@pytest.mark.abort_on_fail
async def test_blocked_missing_config(ops_test: OpsTest) -> None:
"""Test that when charm is missing pbm information that it reports that."""
db_app_name = await get_app_name(ops_test)
await ops_test.model.integrate(S3_APP_NAME, db_app_name)
await ops_test.model.block_until(
lambda: is_relation_joined(ops_test, ENDPOINT, ENDPOINT) is True,
timeout=TIMEOUT,
)

await wait_for_mongodb_units_blocked(
ops_test, db_app_name, status="s3 configurations missing.", timeout=300
)


@pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
@pytest.mark.group(1)
@pytest.mark.abort_on_fail
Expand All @@ -78,14 +95,6 @@ async def test_blocked_incorrect_creds(ops_test: OpsTest) -> None:
action = await s3_integrator_unit.run_action(action_name="sync-s3-credentials", **parameters)
await action.wait()

# relate after s3 becomes active add and wait for relation
await ops_test.model.wait_for_idle(apps=[S3_APP_NAME], status="active")
await ops_test.model.integrate(S3_APP_NAME, db_app_name)
await ops_test.model.block_until(
lambda: is_relation_joined(ops_test, ENDPOINT, ENDPOINT) is True,
timeout=TIMEOUT,
)

# verify that Charmed MongoDB is blocked and reports incorrect credentials
await ops_test.model.wait_for_idle(apps=[S3_APP_NAME], status="active")

Expand Down
49 changes: 38 additions & 11 deletions tests/unit/test_mongodb_backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ def test_get_pbm_status_snap_not_present(self, pbm_command, service):
pbm_command.side_effect = ModelError("service pbm-agent not found")
self.assertTrue(isinstance(self.harness.charm.backups.get_pbm_status(), BlockedStatus))

@patch("charm.MongoDBBackups.are_s3_configurations_provided", return_value=True)
@patch("charm.MongodbOperatorCharm.has_backup_service")
@patch("charm.MongodbOperatorCharm.run_pbm_command")
def test_get_pbm_status_resync(self, pbm_command, service):
def test_get_pbm_status_resync(self, pbm_command, service, *unused):
"""Tests that when pbm is resyncing that pbm is in waiting state."""
relation_id = self.harness.add_relation(RELATION_NAME, "s3-integrator")
self.harness.add_relation_unit(relation_id, "s3-integrator/0")
Expand All @@ -90,9 +91,10 @@ def test_get_pbm_status_resync(self, pbm_command, service):
)
self.assertTrue(isinstance(self.harness.charm.backups.get_pbm_status(), WaitingStatus))

@patch("charm.MongoDBBackups.are_s3_configurations_provided", return_value=True)
@patch("charm.MongodbOperatorCharm.has_backup_service")
@patch("charm.MongodbOperatorCharm.run_pbm_command")
def test_get_pbm_status_running(self, pbm_command, service):
def test_get_pbm_status_running(self, pbm_command, service, *unused):
"""Tests that when pbm not running an op that pbm is in active state."""
relation_id = self.harness.add_relation(RELATION_NAME, "s3-integrator")
self.harness.add_relation_unit(relation_id, "s3-integrator/0")
Expand All @@ -110,7 +112,10 @@ def test_get_pbm_status_incorrect_cred(self, pbm_command, service):

service.return_value = True
pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "status"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm", "status"],
exit_code=1,
stdout="status code: 403",
stderr="",
)
self.assertTrue(isinstance(self.harness.charm.backups.get_pbm_status(), BlockedStatus))

Expand All @@ -123,7 +128,10 @@ def test_get_pbm_status_incorrect_conf(self, pbm_command, service):

service.return_value = True
pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "status"], exit_code=1, stdout="status code: 404", stderr=""
command=["/usr/bin/pbm", "status"],
exit_code=1,
stdout="status code: 404",
stderr="",
)
self.assertTrue(isinstance(self.harness.charm.backups.get_pbm_status(), BlockedStatus))

Expand Down Expand Up @@ -408,10 +416,16 @@ def test_s3_credentials_pbm_error(
service.return_value = True
self.harness.charm.app_peer_data["db_initialised"] = "true"
resync.side_effect = ExecError(
command=["/usr/bin/pbm status"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm status"],
exit_code=1,
stdout="status code: 403",
stderr="",
)
pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm status"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm status"],
exit_code=1,
stdout="status code: 403",
stderr="",
)

# triggering s3 event with correct fields
Expand All @@ -437,7 +451,10 @@ def test_backup_failed(self, pbm_status, pbm_command, service):
service.return_value = True

pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "status"], exit_code=1, stdout="status code: 42", stderr=""
command=["/usr/bin/pbm", "status"],
exit_code=1,
stdout="status code: 42",
stderr="",
)

action_event = mock.Mock()
Expand Down Expand Up @@ -484,7 +501,10 @@ def test_backup_list_wrong_cred(self, pbm_command, service):

service.return_value = True
pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "status"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm", "status"],
exit_code=1,
stdout="status code: 403",
stderr="",
)

self.harness.add_relation(RELATION_NAME, "s3-integrator")
Expand All @@ -504,7 +524,10 @@ def test_backup_list_failed(self, pbm_status, pbm_command, service):
pbm_status.return_value = ActiveStatus("")

pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "list"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm", "list"],
exit_code=1,
stdout="status code: 403",
stderr="",
)

self.harness.add_relation(RELATION_NAME, "s3-integrator")
Expand Down Expand Up @@ -616,7 +639,10 @@ def test_restore_wrong_cred(self, pbm_status, pbm_command, service):
pbm_status.return_value = ActiveStatus("")

pbm_command.side_effect = ExecError(
command=["/usr/bin/pbm", "list"], exit_code=1, stdout="status code: 403", stderr=""
command=["/usr/bin/pbm", "list"],
exit_code=1,
stdout="status code: 403",
stderr="",
)

self.harness.add_relation(RELATION_NAME, "s3-integrator")
Expand Down Expand Up @@ -689,9 +715,10 @@ def test_remap_replicaset_remap_necessary(self, run_pbm_command):
remap = self.harness.charm.backups._remap_replicaset("2002-02-14T13:59:14Z")
self.assertEqual(remap, "current-app-name=old-cluster-name")

@patch("charm.MongoDBBackups.are_s3_configurations_provided", return_value=True)
@patch("charm.MongodbOperatorCharm.has_backup_service")
@patch("charm.MongodbOperatorCharm.run_pbm_command")
def test_get_pbm_status_backup(self, run_pbm_command, service):
def test_get_pbm_status_backup(self, run_pbm_command, service, *unused):
"""Tests that when pbm running a backup that pbm is in maintenance state."""
relation_id = self.harness.add_relation(RELATION_NAME, "s3-integrator")
self.harness.add_relation_unit(relation_id, "s3-integrator/0")
Expand Down
Loading