From 36302c6294a97da037952d5bc1f6d643e1f60576 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Thu, 7 Mar 2024 16:13:40 +0200 Subject: [PATCH 01/18] Legacy endpoints: relate legacy+modern endpoints simultaneously (fool-protection check) --- .../integration/ha_tests/test_self_healing.py | 48 +++++++ tests/integration/relations/__init__.py | 2 + tests/integration/relations/helpers.py | 53 ++++++++ tests/integration/relations/test_relations.py | 125 ++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 tests/integration/relations/__init__.py create mode 100644 tests/integration/relations/helpers.py create mode 100644 tests/integration/relations/test_relations.py diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 0dcc43be02..c6da62a486 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -545,3 +545,51 @@ async def test_network_cut_without_ip_change( ), "Connection is not possible after network restore" await is_cluster_updated(ops_test, primary_name, use_ip_from_inside=True) + + +@pytest.mark.group(1) +async def test_legacy_modern_endpoints(ops_test: OpsTest): + """Scale the database to zero units and scale up again.""" + wait_for_apps = False + # It is possible for users to provide their own cluster for HA testing. Hence, check if there + # is a pre-existing cluster. + if not await app_name(ops_test): + wait_for_apps = True + charm = await ops_test.build_charm(".") + async with ops_test.fast_forward(): + await ops_test.model.deploy( + charm, + num_units=1, + series=CHARM_SERIES, + storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, + config={"profile": "testing"}, + ) + # Deploy the continuous writes application charm if it wasn't already deployed. + if not await app_name(ops_test, APPLICATION_NAME): + wait_for_apps = True + async with ops_test.fast_forward(): + await ops_test.model.deploy( + APPLICATION_NAME, + application_name=APPLICATION_NAME, + series=CHARM_SERIES, + channel="edge", + ) + + if not await app_name(ops_test, "mailman3-core"): + await ops_test.model.deploy( + "mailman3-core", + channel="stable", + application_name="mailman3-core", + config={"hostname": "example.org"}, + ) + + if wait_for_apps: + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(status="active", timeout=3000) + + # dbname = f"{APPLICATION_NAME.replace('-', '_')}_first_database" + + await ops_test.model.relate("mailman3-core", f"{APP_NAME}:db") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_NAME}:first-database") + + await ops_test.model.wait_for_idle(status="active", timeout=1000) diff --git a/tests/integration/relations/__init__.py b/tests/integration/relations/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/relations/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/relations/helpers.py b/tests/integration/relations/helpers.py new file mode 100644 index 0000000000..cb34ddc026 --- /dev/null +++ b/tests/integration/relations/helpers.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +from typing import Optional + +import yaml +from pytest_operator.plugin import OpsTest + + +async def get_legacy_db_connection_str( + ops_test: OpsTest, + application_name: str, + relation_name: str, + read_only_endpoint: bool = False, + remote_unit_name: str = None, +) -> Optional[str]: + """Returns a PostgreSQL connection string. + + Args: + ops_test: The ops test framework instance + application_name: The name of the application + relation_name: name of the relation to get connection data from + read_only_endpoint: whether to choose the read-only endpoint + instead of the read/write endpoint + remote_unit_name: Optional remote unit name used to retrieve + unit data instead of application data + + Returns: + a PostgreSQL connection string + """ + unit_name = f"{application_name}/0" + raw_data = (await ops_test.juju("show-unit", unit_name))[1] + if not raw_data: + raise ValueError(f"no unit info could be grabbed for {unit_name}") + data = yaml.safe_load(raw_data) + # Filter the data based on the relation name. + relation_data = [ + v for v in data[unit_name]["relation-info"] if v["related-endpoint"] == relation_name + ] + if len(relation_data) == 0: + raise ValueError( + f"no relation data could be grabbed on relation with endpoint {relation_name}" + ) + if remote_unit_name: + data = relation_data[0]["related-units"][remote_unit_name]["data"] + else: + data = relation_data[0]["application-data"] + if read_only_endpoint: + if data.get("standbys") is None: + return None + return data.get("standbys").split(",")[0] + else: + return data.get("master") diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py new file mode 100644 index 0000000000..55751c556b --- /dev/null +++ b/tests/integration/relations/test_relations.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +import asyncio +import logging + +import psycopg2 +import pytest +from pytest_operator.plugin import OpsTest +from tenacity import Retrying, stop_after_delay, wait_fixed + +from ..helpers import ( + CHARM_SERIES, + DATABASE_APP_NAME, + METADATA, +) +from ..new_relations.helpers import build_connection_string +from ..new_relations.test_new_relations import APPLICATION_APP_NAME +from .helpers import get_legacy_db_connection_str + +logger = logging.getLogger(__name__) + +APP_NAME = METADATA["name"] +MAILMAN3_CORE_APP_NAME = "mailman3-core" +DB_RELATION = "db" +DATABASE_RELATION = "database" +FIRST_DATABASE_RELATION = "first-database" +APP_NAMES = [APP_NAME, APPLICATION_APP_NAME, MAILMAN3_CORE_APP_NAME] + + +@pytest.mark.group(1) +@pytest.mark.abort_on_fail +async def test_deploy_charms(ops_test: OpsTest, charm): + """Deploy both charms (application and database) to use in the tests.""" + # Deploy both charms (multiple units for each application to test that later they correctly + # set data in the relation application databag using only the leader unit). + async with ops_test.fast_forward(): + await asyncio.gather( + ops_test.model.deploy( + APPLICATION_APP_NAME, + application_name=APPLICATION_APP_NAME, + num_units=1, + series=CHARM_SERIES, + channel="edge", + ), + ops_test.model.deploy( + charm, + application_name=DATABASE_APP_NAME, + num_units=1, + series=CHARM_SERIES, + config={"profile": "testing"}, + ), + ops_test.model.deploy( + MAILMAN3_CORE_APP_NAME, + application_name=MAILMAN3_CORE_APP_NAME, + channel="stable", + config={"hostname": "example.org"}, + ), + ) + + await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) + + +@pytest.mark.group(1) +async def test_legacy_modern_endpoints(ops_test: OpsTest): + """Test relation legacy (db interface) and modern (database interface) endpoints simultaneously.""" + async with ops_test.fast_forward(): + await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + + await ops_test.model.wait_for_idle(status="active", timeout=1000) + + # Get the connection string to connect to the database using the read/write endpoint. + modern_interface_connect = await build_connection_string( + ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION + ) + legacy_interface_connect = await get_legacy_db_connection_str( + ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" + ) + + # Connect to PostgreSQL by database interface. + logger.info(f"connecting to the database [{modern_interface_connect}]") + for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): + with attempt: + with psycopg2.connect(modern_interface_connect) as connection: + assert connection.status == psycopg2.extensions.STATUS_READY + + # Connect to PostgreSQL by db interface. + logger.info(f"connecting to the database [{legacy_interface_connect}]") + for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): + with attempt: + with psycopg2.connect(legacy_interface_connect) as connection: + assert connection.status == psycopg2.extensions.STATUS_READY + + async with ops_test.fast_forward(): + # Remove relation and check connect to Postgresql. + logger.info( + f"remove relation {APP_NAME}:{DB_RELATION} = {MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" + ) + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DB_RELATION}", f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" + ) + await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) + with pytest.raises(psycopg2.OperationalError): + logger.info( + f"check connect to database after remove relation: interface {DB_RELATION}" + ) + psycopg2.connect(legacy_interface_connect) + + async with ops_test.fast_forward(): + # Remove relation and check connect to Postgresql. + logger.info( + f"remove relation {APP_NAME}:{DATABASE_RELATION} = {APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + ) + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + ) + await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) + for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): + with attempt: + with pytest.raises(psycopg2.OperationalError): + logger.info( + f"check connect to database after remove relation: interface {DATABASE_RELATION}" + ) + psycopg2.connect(modern_interface_connect) From 50bf245c1434589112bde180f967db1217ed3bf7 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Tue, 12 Mar 2024 23:06:09 +0200 Subject: [PATCH 02/18] mv func --- .../integration/ha_tests/test_self_healing.py | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index c6da62a486..0dcc43be02 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -545,51 +545,3 @@ async def test_network_cut_without_ip_change( ), "Connection is not possible after network restore" await is_cluster_updated(ops_test, primary_name, use_ip_from_inside=True) - - -@pytest.mark.group(1) -async def test_legacy_modern_endpoints(ops_test: OpsTest): - """Scale the database to zero units and scale up again.""" - wait_for_apps = False - # It is possible for users to provide their own cluster for HA testing. Hence, check if there - # is a pre-existing cluster. - if not await app_name(ops_test): - wait_for_apps = True - charm = await ops_test.build_charm(".") - async with ops_test.fast_forward(): - await ops_test.model.deploy( - charm, - num_units=1, - series=CHARM_SERIES, - storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, - config={"profile": "testing"}, - ) - # Deploy the continuous writes application charm if it wasn't already deployed. - if not await app_name(ops_test, APPLICATION_NAME): - wait_for_apps = True - async with ops_test.fast_forward(): - await ops_test.model.deploy( - APPLICATION_NAME, - application_name=APPLICATION_NAME, - series=CHARM_SERIES, - channel="edge", - ) - - if not await app_name(ops_test, "mailman3-core"): - await ops_test.model.deploy( - "mailman3-core", - channel="stable", - application_name="mailman3-core", - config={"hostname": "example.org"}, - ) - - if wait_for_apps: - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle(status="active", timeout=3000) - - # dbname = f"{APPLICATION_NAME.replace('-', '_')}_first_database" - - await ops_test.model.relate("mailman3-core", f"{APP_NAME}:db") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_NAME}:first-database") - - await ops_test.model.wait_for_idle(status="active", timeout=1000) From f463263503f6fbabb74c6d1559b44e019199372b Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 20 Mar 2024 14:49:45 +0200 Subject: [PATCH 03/18] test legacy + modern endpoints --- src/constants.py | 1 + src/relations/db.py | 51 +++++++-- src/relations/postgresql_provider.py | 47 +++++++- tests/integration/relations/test_relations.py | 101 ++++++++++-------- 4 files changed, 143 insertions(+), 57 deletions(-) diff --git a/src/constants.py b/src/constants.py index 3f0d79114c..208c876e3f 100644 --- a/src/constants.py +++ b/src/constants.py @@ -11,6 +11,7 @@ LEGACY_DB_ADMIN = "db-admin" PEER = "database-peers" ALL_CLIENT_RELATIONS = [DATABASE, LEGACY_DB, LEGACY_DB_ADMIN] +ALL_LEGACY_RELATIONS = [LEGACY_DB, LEGACY_DB_ADMIN] API_REQUEST_TIMEOUT = 5 PATRONI_CLUSTER_STATUS_ENDPOINT = "cluster" BACKUP_USER = "backup" diff --git a/src/relations/db.py b/src/relations/db.py index 56a28db1a8..d16661bbcf 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -3,7 +3,6 @@ """Library containing the implementation of the legacy db and db-admin relations.""" - import logging from typing import Iterable, List, Set, Tuple @@ -25,6 +24,8 @@ from constants import APP_SCOPE, DATABASE_PORT from utils import new_password +from src.constants import ALL_LEGACY_RELATIONS + logger = logging.getLogger(__name__) EXTENSIONS_BLOCKING_MESSAGE = ( @@ -35,6 +36,10 @@ "roles requested through relation, use postgresql_client interface instead" ) +ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( + "Please choose one endpoint to use. No need to relate all of them simultaneously!" +) + class DbProvides(Object): """Defines functionality for the 'provides' side of the 'db' relation. @@ -88,6 +93,20 @@ def _check_for_blocking_relations(self, relation_id: int) -> bool: return True return False + def _check_exist_current_relation(self) -> bool: + for r in self.charm.client_relations: + if r in ALL_LEGACY_RELATIONS: + return True + return False + + def _check_relation_another_endpoint(self) -> bool: + """Checks if there are relations with other endpoints.""" + is_exist = self._check_exist_current_relation() + for relation in self.charm.client_relations: + if relation.name not in ALL_LEGACY_RELATIONS and is_exist: + return True + return False + def _on_relation_changed(self, event: RelationChangedEvent) -> None: """Handle the legacy db/db-admin relation changed event. @@ -97,10 +116,14 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None: if not self.charm.unit.is_leader(): return + if self._check_relation_another_endpoint(): + self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE) + return + if ( - "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Deferring on_relation_changed: cluster not initialized, Patroni not started or primary endpoint not available" @@ -223,9 +246,9 @@ def _on_relation_departed(self, event: RelationDepartedEvent) -> None: return if ( - "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Deferring on_relation_departed: cluster not initialized, Patroni not started or primary endpoint not available" @@ -248,10 +271,10 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None: """Remove the user created for this relation.""" # Check for some conditions before trying to access the PostgreSQL instance. if ( - not self.charm.unit.is_leader() - or "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + not self.charm.unit.is_leader() + or "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Early exit on_relation_broken: Not leader, cluster not initialized, Patroni not started or no primary endpoint" @@ -279,6 +302,12 @@ def _update_unit_status(self, relation: Relation) -> None: if not self._check_for_blocking_relations(relation.id): self.charm.unit.status = ActiveStatus() + def _update_unit_status_on_blocking_endpoint_simultaneously(self): + """# Clean up Blocked status if this is due related of multiple endpoints.""" + if self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE: + if not self._check_multiple_endpoints(): + self.charm.unit.status = ActiveStatus() + def update_endpoints(self, relation: Relation = None) -> None: """Set the read/write and read-only endpoints.""" # Get the current relation or all the relations diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index 60ded24bc9..79f3115a9b 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -8,7 +8,7 @@ from charms.data_platform_libs.v0.data_interfaces import ( DatabaseProvides, - DatabaseRequestedEvent, + DatabaseRequestedEvent, diff, ) from charms.postgresql_k8s.v0.postgresql import ( INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE, @@ -18,7 +18,7 @@ PostgreSQLGetPostgreSQLVersionError, PostgreSQLListUsersError, ) -from ops.charm import CharmBase, RelationBrokenEvent +from ops.charm import CharmBase, RelationBrokenEvent, RelationChangedEvent from ops.framework import Object from ops.model import ActiveStatus, BlockedStatus, Relation @@ -28,6 +28,10 @@ logger = logging.getLogger(__name__) +ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( + "Please choose one endpoint to use. No need to relate all of them simultaneously!" +) + class PostgreSQLProvider(Object): """Defines functionality for the 'provides' side of the 'postgresql-client' relation. @@ -49,7 +53,10 @@ def __init__(self, charm: CharmBase, relation_name: str = "database") -> None: self.framework.observe( charm.on[self.relation_name].relation_broken, self._on_relation_broken ) - + self.framework.observe( + charm.on[self.relation_name].relation_changed, + self._on_relation_changed_event, + ) self.charm = charm # Charm events defined in the database provides charm library. @@ -190,6 +197,13 @@ def update_endpoints(self, event: DatabaseRequestedEvent = None) -> None: read_only_endpoints, ) + def _check_multiple_endpoints(self) -> bool: + """Checks if there are relations with other endpoints.""" + relation_names = [relation.name for relation in self.charm.client_relations] + if "database" in relation_names and len(relation_names) > 1: + return True + return False + def _update_unit_status(self, relation: Relation) -> None: """# Clean up Blocked status if it's due to extensions request.""" if ( @@ -199,6 +213,33 @@ def _update_unit_status(self, relation: Relation) -> None: if not self.check_for_invalid_extra_user_roles(relation.id): self.charm.unit.status = ActiveStatus() + self._update_unit_status_on_blocking_endpoint_simultaneously() + + def _on_relation_changed_event(self, event: RelationChangedEvent) -> None: + """Event emitted when the relation has changed.""" + # Leader only + if not self.charm.unit.is_leader(): + return + + if self._check_multiple_endpoints(): + self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE) + return + # Check which data has changed to emit customs events. + _diff = diff(event, self.charm.unit) + + # Emit a database requested event if the setup key (database name and optional + # extra user roles) was added to the relation databag by the application. + if "database" in _diff.added: + getattr(self.database_provides.on, "database_requested").emit( + event.relation, app=event.app, unit=event.unit + ) + + def _update_unit_status_on_blocking_endpoint_simultaneously(self): + """# Clean up Blocked status if this is due related of multiple endpoints.""" + if self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE: + if not self._check_multiple_endpoints(): + self.charm.unit.status = ActiveStatus() + def check_for_invalid_extra_user_roles(self, relation_id: int) -> bool: """Checks if there are relations with invalid extra user roles. diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 55751c556b..7c3b78caf2 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -3,20 +3,21 @@ # See LICENSE file for licensing details. import asyncio import logging +import secrets +import string +from pathlib import Path +from time import sleep import psycopg2 import pytest -from pytest_operator.plugin import OpsTest +import yaml +from pytest_operator.plugin import OpsTest, FileResource from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import ( - CHARM_SERIES, - DATABASE_APP_NAME, - METADATA, -) -from ..new_relations.helpers import build_connection_string +from ..helpers import CHARM_SERIES, DATABASE_APP_NAME, METADATA from ..new_relations.test_new_relations import APPLICATION_APP_NAME -from .helpers import get_legacy_db_connection_str +from ..new_relations.test_new_relations import build_connection_string +from ..relations.helpers import get_legacy_db_connection_str logger = logging.getLogger(__name__) @@ -60,58 +61,75 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) - @pytest.mark.group(1) async def test_legacy_modern_endpoints(ops_test: OpsTest): - """Test relation legacy (db interface) and modern (database interface) endpoints simultaneously.""" - async with ops_test.fast_forward(): - await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") - - await ops_test.model.wait_for_idle(status="active", timeout=1000) + await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") - # Get the connection string to connect to the database using the read/write endpoint. - modern_interface_connect = await build_connection_string( - ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION - ) - legacy_interface_connect = await get_legacy_db_connection_str( - ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" + app = ops_test.model.applications[APP_NAME] + await ops_test.model.block_until( + lambda: "blocked" in {unit.workload_status for unit in app.units}, + timeout=1500, ) - # Connect to PostgreSQL by database interface. - logger.info(f"connecting to the database [{modern_interface_connect}]") - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt: - with psycopg2.connect(modern_interface_connect) as connection: - assert connection.status == psycopg2.extensions.STATUS_READY + logger.info(f" remove relation with modern endpoints") + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + ) + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle( + status="active", + timeout=1500, + raise_on_error=False, + ) - # Connect to PostgreSQL by db interface. - logger.info(f"connecting to the database [{legacy_interface_connect}]") + legacy_interface_connect = await get_legacy_db_connection_str(ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, + remote_unit_name=f"{APP_NAME}/0") + logger.info(f" check connect to = {legacy_interface_connect}") for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): with attempt: with psycopg2.connect(legacy_interface_connect) as connection: assert connection.status == psycopg2.extensions.STATUS_READY + logger.info(f"==== remove relation mailman3-core") async with ops_test.fast_forward(): - # Remove relation and check connect to Postgresql. - logger.info( - f"remove relation {APP_NAME}:{DB_RELATION} = {MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" - ) await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DB_RELATION}", f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" + f"{APP_NAME}:db", f"mailman3-core:db" ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) with pytest.raises(psycopg2.OperationalError): - logger.info( - f"check connect to database after remove relation: interface {DB_RELATION}" - ) psycopg2.connect(legacy_interface_connect) +async def test_legacy_modern_endpoints_a(ops_test: OpsTest): + await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + + app = ops_test.model.applications[APP_NAME] + await ops_test.model.block_until( + lambda: "blocked" in {unit.workload_status for unit in app.units}, + timeout=1500, + ) + + logger.info(f" remove relation with legacy endpoints") + await ops_test.model.applications[APP_NAME].remove_relation( + f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" + ) async with ops_test.fast_forward(): - # Remove relation and check connect to Postgresql. - logger.info( - f"remove relation {APP_NAME}:{DATABASE_RELATION} = {APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + await ops_test.model.wait_for_idle( + status="active", + timeout=3000, + raise_on_error=False ) + + modern_interface_connect = await build_connection_string(ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION) + logger.info(f" ==================== modern database_unit_name ={modern_interface_connect}") + for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): + with attempt: + with psycopg2.connect(modern_interface_connect) as connection: + assert connection.status == psycopg2.extensions.STATUS_READY + + logger.info(f"==== modern remove relation {APPLICATION_APP_NAME}") + async with ops_test.fast_forward(): await ops_test.model.applications[APP_NAME].remove_relation( f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" ) @@ -119,7 +137,4 @@ async def test_legacy_modern_endpoints(ops_test: OpsTest): for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): with attempt: with pytest.raises(psycopg2.OperationalError): - logger.info( - f"check connect to database after remove relation: interface {DATABASE_RELATION}" - ) psycopg2.connect(modern_interface_connect) From 0dbfbc2b08ccd5b2879c74f5fa4ab6c99840998d Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 20 Mar 2024 15:00:23 +0200 Subject: [PATCH 04/18] test legacy + modern endpoints --- tests/integration/relations/test_relations.py | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 7c3b78caf2..4847b8598d 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -61,80 +61,80 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) -@pytest.mark.group(1) -async def test_legacy_modern_endpoints(ops_test: OpsTest): - await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") - - app = ops_test.model.applications[APP_NAME] - await ops_test.model.block_until( - lambda: "blocked" in {unit.workload_status for unit in app.units}, - timeout=1500, - ) - - logger.info(f" remove relation with modern endpoints") - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" - ) - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle( - status="active", - timeout=1500, - raise_on_error=False, - ) - - legacy_interface_connect = await get_legacy_db_connection_str(ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, - remote_unit_name=f"{APP_NAME}/0") - logger.info(f" check connect to = {legacy_interface_connect}") - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt: - with psycopg2.connect(legacy_interface_connect) as connection: - assert connection.status == psycopg2.extensions.STATUS_READY - - logger.info(f"==== remove relation mailman3-core") - async with ops_test.fast_forward(): - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:db", f"mailman3-core:db" - ) - await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) - with pytest.raises(psycopg2.OperationalError): - psycopg2.connect(legacy_interface_connect) - -async def test_legacy_modern_endpoints_a(ops_test: OpsTest): - await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") - - app = ops_test.model.applications[APP_NAME] - await ops_test.model.block_until( - lambda: "blocked" in {unit.workload_status for unit in app.units}, - timeout=1500, - ) - - logger.info(f" remove relation with legacy endpoints") - await ops_test.model.applications[APP_NAME].remove_relation( - f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" - ) - async with ops_test.fast_forward(): - await ops_test.model.wait_for_idle( - status="active", - timeout=3000, - raise_on_error=False - ) - - modern_interface_connect = await build_connection_string(ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION) - logger.info(f" ==================== modern database_unit_name ={modern_interface_connect}") - for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): - with attempt: - with psycopg2.connect(modern_interface_connect) as connection: - assert connection.status == psycopg2.extensions.STATUS_READY - - logger.info(f"==== modern remove relation {APPLICATION_APP_NAME}") - async with ops_test.fast_forward(): - await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" - ) - await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) - for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): - with attempt: - with pytest.raises(psycopg2.OperationalError): - psycopg2.connect(modern_interface_connect) +# @pytest.mark.group(1) +# async def test_legacy_modern_endpoints(ops_test: OpsTest): +# await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") +# await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") +# +# app = ops_test.model.applications[APP_NAME] +# await ops_test.model.block_until( +# lambda: "blocked" in {unit.workload_status for unit in app.units}, +# timeout=1500, +# ) +# +# logger.info(f" remove relation with modern endpoints") +# await ops_test.model.applications[APP_NAME].remove_relation( +# f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" +# ) +# async with ops_test.fast_forward(): +# await ops_test.model.wait_for_idle( +# status="active", +# timeout=1500, +# raise_on_error=False, +# ) +# +# legacy_interface_connect = await get_legacy_db_connection_str(ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, +# remote_unit_name=f"{APP_NAME}/0") +# logger.info(f" check connect to = {legacy_interface_connect}") +# for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): +# with attempt: +# with psycopg2.connect(legacy_interface_connect) as connection: +# assert connection.status == psycopg2.extensions.STATUS_READY +# +# logger.info(f"==== remove relation mailman3-core") +# async with ops_test.fast_forward(): +# await ops_test.model.applications[APP_NAME].remove_relation( +# f"{APP_NAME}:db", f"mailman3-core:db" +# ) +# await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) +# with pytest.raises(psycopg2.OperationalError): +# psycopg2.connect(legacy_interface_connect) +# +# async def test_legacy_modern_endpoints_a(ops_test: OpsTest): +# await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") +# await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") +# +# app = ops_test.model.applications[APP_NAME] +# await ops_test.model.block_until( +# lambda: "blocked" in {unit.workload_status for unit in app.units}, +# timeout=1500, +# ) +# +# logger.info(f" remove relation with legacy endpoints") +# await ops_test.model.applications[APP_NAME].remove_relation( +# f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" +# ) +# async with ops_test.fast_forward(): +# await ops_test.model.wait_for_idle( +# status="active", +# timeout=3000, +# raise_on_error=False +# ) +# +# modern_interface_connect = await build_connection_string(ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION) +# logger.info(f" ==================== modern database_unit_name ={modern_interface_connect}") +# for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): +# with attempt: +# with psycopg2.connect(modern_interface_connect) as connection: +# assert connection.status == psycopg2.extensions.STATUS_READY +# +# logger.info(f"==== modern remove relation {APPLICATION_APP_NAME}") +# async with ops_test.fast_forward(): +# await ops_test.model.applications[APP_NAME].remove_relation( +# f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" +# ) +# await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) +# for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): +# with attempt: +# with pytest.raises(psycopg2.OperationalError): +# psycopg2.connect(modern_interface_connect) From bab684260e7301e2c0f9266dfc8438acf7950282 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 20 Mar 2024 16:10:01 +0200 Subject: [PATCH 05/18] test relate legacy+modern endpoints simultaneously --- src/relations/db.py | 29 +-- src/relations/postgresql_provider.py | 9 +- tests/integration/relations/test_relations.py | 165 +++++++++--------- 3 files changed, 102 insertions(+), 101 deletions(-) diff --git a/src/relations/db.py b/src/relations/db.py index ef69f9c8c1..9fabe82feb 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -21,11 +21,9 @@ from ops.model import ActiveStatus, BlockedStatus, Relation, Unit from pgconnstr import ConnectionString -from constants import APP_SCOPE, DATABASE_PORT +from constants import ALL_LEGACY_RELATIONS, APP_SCOPE, DATABASE_PORT from utils import new_password -from src.constants import ALL_LEGACY_RELATIONS - logger = logging.getLogger(__name__) EXTENSIONS_BLOCKING_MESSAGE = ( @@ -121,9 +119,9 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None: return if ( - "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Deferring on_relation_changed: cluster not initialized, Patroni not started or primary endpoint not available" @@ -246,9 +244,9 @@ def _on_relation_departed(self, event: RelationDepartedEvent) -> None: return if ( - "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Deferring on_relation_departed: cluster not initialized, Patroni not started or primary endpoint not available" @@ -271,10 +269,10 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None: """Remove the user created for this relation.""" # Check for some conditions before trying to access the PostgreSQL instance. if ( - not self.charm.unit.is_leader() - or "cluster_initialised" not in self.charm._peers.data[self.charm.app] - or not self.charm._patroni.member_started - or not self.charm.primary_endpoint + not self.charm.unit.is_leader() + or "cluster_initialised" not in self.charm._peers.data[self.charm.app] + or not self.charm._patroni.member_started + or not self.charm.primary_endpoint ): logger.debug( "Early exit on_relation_broken: Not leader, cluster not initialized, Patroni not started or no primary endpoint" @@ -304,7 +302,10 @@ def _update_unit_status(self, relation: Relation) -> None: def _update_unit_status_on_blocking_endpoint_simultaneously(self): """# Clean up Blocked status if this is due related of multiple endpoints.""" - if self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE: + if ( + self.charm.is_blocked + and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE + ): if not self._check_multiple_endpoints(): self.charm.unit.status = ActiveStatus() diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index ad9fd5dc54..ecfcaadce9 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -7,7 +7,8 @@ from charms.data_platform_libs.v0.data_interfaces import ( DatabaseProvides, - DatabaseRequestedEvent, diff, + DatabaseRequestedEvent, + diff, ) from charms.postgresql_k8s.v0.postgresql import ( INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE, @@ -31,6 +32,7 @@ "Please choose one endpoint to use. No need to relate all of them simultaneously!" ) + class PostgreSQLProvider(Object): """Defines functionality for the 'provides' side of the 'postgresql-client' relation. @@ -236,7 +238,10 @@ def _on_relation_changed_event(self, event: RelationChangedEvent) -> None: def _update_unit_status_on_blocking_endpoint_simultaneously(self): """# Clean up Blocked status if this is due related of multiple endpoints.""" - if self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE: + if ( + self.charm.is_blocked + and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE + ): if not self._check_multiple_endpoints(): self.charm.unit.status = ActiveStatus() diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 4847b8598d..e7496b027d 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -3,20 +3,14 @@ # See LICENSE file for licensing details. import asyncio import logging -import secrets -import string -from pathlib import Path -from time import sleep import psycopg2 import pytest -import yaml -from pytest_operator.plugin import OpsTest, FileResource +from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed from ..helpers import CHARM_SERIES, DATABASE_APP_NAME, METADATA -from ..new_relations.test_new_relations import APPLICATION_APP_NAME -from ..new_relations.test_new_relations import build_connection_string +from ..new_relations.test_new_relations import APPLICATION_APP_NAME, build_connection_string from ..relations.helpers import get_legacy_db_connection_str logger = logging.getLogger(__name__) @@ -61,80 +55,81 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", timeout=3000) -# @pytest.mark.group(1) -# async def test_legacy_modern_endpoints(ops_test: OpsTest): -# await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") -# await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") -# -# app = ops_test.model.applications[APP_NAME] -# await ops_test.model.block_until( -# lambda: "blocked" in {unit.workload_status for unit in app.units}, -# timeout=1500, -# ) -# -# logger.info(f" remove relation with modern endpoints") -# await ops_test.model.applications[APP_NAME].remove_relation( -# f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" -# ) -# async with ops_test.fast_forward(): -# await ops_test.model.wait_for_idle( -# status="active", -# timeout=1500, -# raise_on_error=False, -# ) -# -# legacy_interface_connect = await get_legacy_db_connection_str(ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, -# remote_unit_name=f"{APP_NAME}/0") -# logger.info(f" check connect to = {legacy_interface_connect}") -# for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): -# with attempt: -# with psycopg2.connect(legacy_interface_connect) as connection: -# assert connection.status == psycopg2.extensions.STATUS_READY -# -# logger.info(f"==== remove relation mailman3-core") -# async with ops_test.fast_forward(): -# await ops_test.model.applications[APP_NAME].remove_relation( -# f"{APP_NAME}:db", f"mailman3-core:db" -# ) -# await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) -# with pytest.raises(psycopg2.OperationalError): -# psycopg2.connect(legacy_interface_connect) -# -# async def test_legacy_modern_endpoints_a(ops_test: OpsTest): -# await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") -# await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") -# -# app = ops_test.model.applications[APP_NAME] -# await ops_test.model.block_until( -# lambda: "blocked" in {unit.workload_status for unit in app.units}, -# timeout=1500, -# ) -# -# logger.info(f" remove relation with legacy endpoints") -# await ops_test.model.applications[APP_NAME].remove_relation( -# f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" -# ) -# async with ops_test.fast_forward(): -# await ops_test.model.wait_for_idle( -# status="active", -# timeout=3000, -# raise_on_error=False -# ) -# -# modern_interface_connect = await build_connection_string(ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION) -# logger.info(f" ==================== modern database_unit_name ={modern_interface_connect}") -# for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): -# with attempt: -# with psycopg2.connect(modern_interface_connect) as connection: -# assert connection.status == psycopg2.extensions.STATUS_READY -# -# logger.info(f"==== modern remove relation {APPLICATION_APP_NAME}") -# async with ops_test.fast_forward(): -# await ops_test.model.applications[APP_NAME].remove_relation( -# f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" -# ) -# await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) -# for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): -# with attempt: -# with pytest.raises(psycopg2.OperationalError): -# psycopg2.connect(modern_interface_connect) + +@pytest.mark.group(1) +async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): + await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + + app = ops_test.model.applications[APP_NAME] + await ops_test.model.block_until( + lambda: "blocked" in {unit.workload_status for unit in app.units}, + timeout=1500, + ) + + logger.info(" remove relation with modern endpoints") + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + ) + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle( + status="active", + timeout=1500, + raise_on_error=False, + ) + + legacy_interface_connect = await get_legacy_db_connection_str( + ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" + ) + logger.info(f" check connect to = {legacy_interface_connect}") + for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): + with attempt: + with psycopg2.connect(legacy_interface_connect) as connection: + assert connection.status == psycopg2.extensions.STATUS_READY + + logger.info(" remove relation mailman3-core") + async with ops_test.fast_forward(): + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DB_RELATION}", f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" + ) + await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) + with pytest.raises(psycopg2.OperationalError): + psycopg2.connect(legacy_interface_connect) + + +async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): + await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + + app = ops_test.model.applications[APP_NAME] + await ops_test.model.block_until( + lambda: "blocked" in {unit.workload_status for unit in app.units}, + timeout=1500, + ) + + logger.info(" remove relation with legacy endpoints") + await ops_test.model.applications[APP_NAME].remove_relation( + f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" + ) + async with ops_test.fast_forward(): + await ops_test.model.wait_for_idle(status="active", timeout=3000, raise_on_error=False) + + modern_interface_connect = await build_connection_string( + ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION + ) + logger.info(f" check connect to = {modern_interface_connect}") + for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): + with attempt: + with psycopg2.connect(modern_interface_connect) as connection: + assert connection.status == psycopg2.extensions.STATUS_READY + + logger.info(f" remove relation {APPLICATION_APP_NAME}") + async with ops_test.fast_forward(): + await ops_test.model.applications[APP_NAME].remove_relation( + f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + ) + await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) + for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): + with attempt: + with pytest.raises(psycopg2.OperationalError): + psycopg2.connect(modern_interface_connect) From 3c2968cf9abdeaf8e331a09c2db626541cb023db Mon Sep 17 00:00:00 2001 From: BalabaDmintri <78904505+BalabaDmintri@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:04:58 +0200 Subject: [PATCH 06/18] Update src/relations/postgresql_provider.py Co-authored-by: Marcelo Henrique Neppel --- src/relations/postgresql_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index ecfcaadce9..37962db1b0 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -237,7 +237,7 @@ def _on_relation_changed_event(self, event: RelationChangedEvent) -> None: ) def _update_unit_status_on_blocking_endpoint_simultaneously(self): - """# Clean up Blocked status if this is due related of multiple endpoints.""" + """Clean up Blocked status if this is due related of multiple endpoints.""" if ( self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE From f8426fe14e571b836e572d23bdaac3a2e1c9b497 Mon Sep 17 00:00:00 2001 From: BalabaDmintri <78904505+BalabaDmintri@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:05:07 +0200 Subject: [PATCH 07/18] Update tests/integration/relations/helpers.py Co-authored-by: Marcelo Henrique Neppel --- tests/integration/relations/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/relations/helpers.py b/tests/integration/relations/helpers.py index cb34ddc026..99e1b7e3bb 100644 --- a/tests/integration/relations/helpers.py +++ b/tests/integration/relations/helpers.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2022 Canonical Ltd. +# Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. from typing import Optional From 809b8094483ec9a400c40144fd8e0e4ca8bffbc1 Mon Sep 17 00:00:00 2001 From: BalabaDmintri <78904505+BalabaDmintri@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:05:34 +0200 Subject: [PATCH 08/18] Update tests/integration/relations/test_relations.py Co-authored-by: Marcelo Henrique Neppel --- tests/integration/relations/test_relations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index e7496b027d..d484aa5675 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2022 Canonical Ltd. +# Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. import asyncio import logging From caa7350fec0ff12fdf96242f697c50b62dc598fc Mon Sep 17 00:00:00 2001 From: BalabaDmintri <78904505+BalabaDmintri@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:05:55 +0200 Subject: [PATCH 09/18] Update tests/integration/relations/test_relations.py Co-authored-by: Marcelo Henrique Neppel --- tests/integration/relations/test_relations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index d484aa5675..1c13232835 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -40,7 +40,7 @@ async def test_deploy_charms(ops_test: OpsTest, charm): ), ops_test.model.deploy( charm, - application_name=DATABASE_APP_NAME, + application_name=APP_NAME, num_units=1, series=CHARM_SERIES, config={"profile": "testing"}, From b836019c4a1f8f9dc838710002ebbf20ad2709f2 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Fri, 22 Mar 2024 15:24:54 +0200 Subject: [PATCH 10/18] check deploy postgresql --- src/constants.py | 4 ++++ src/relations/db.py | 10 +++------- src/relations/postgresql_provider.py | 10 ++-------- tests/integration/relations/test_relations.py | 1 + 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/constants.py b/src/constants.py index 208c876e3f..6af08bfafc 100644 --- a/src/constants.py +++ b/src/constants.py @@ -68,3 +68,7 @@ UNIT_SCOPE = "unit" SECRET_KEY_OVERRIDES = {"ca": "cauth"} + +ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( + "Please choose one endpoint to use. No need to relate all of them simultaneously!" +) \ No newline at end of file diff --git a/src/relations/db.py b/src/relations/db.py index 9fabe82feb..50f5b37a7d 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -21,7 +21,7 @@ from ops.model import ActiveStatus, BlockedStatus, Relation, Unit from pgconnstr import ConnectionString -from constants import ALL_LEGACY_RELATIONS, APP_SCOPE, DATABASE_PORT +from constants import ALL_LEGACY_RELATIONS, APP_SCOPE, DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE from utils import new_password logger = logging.getLogger(__name__) @@ -34,11 +34,6 @@ "roles requested through relation, use postgresql_client interface instead" ) -ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( - "Please choose one endpoint to use. No need to relate all of them simultaneously!" -) - - class DbProvides(Object): """Defines functionality for the 'provides' side of the 'db' relation. @@ -299,9 +294,10 @@ def _update_unit_status(self, relation: Relation) -> None: ]: if not self._check_for_blocking_relations(relation.id): self.charm.unit.status = ActiveStatus() + self._update_unit_status_on_blocking_endpoint_simultaneously() def _update_unit_status_on_blocking_endpoint_simultaneously(self): - """# Clean up Blocked status if this is due related of multiple endpoints.""" + """Clean up Blocked status if this is due related of multiple endpoints.""" if ( self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index 37962db1b0..cffc1bbdb5 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -22,17 +22,11 @@ from ops.framework import Object from ops.model import ActiveStatus, BlockedStatus, Relation -from constants import ALL_CLIENT_RELATIONS, APP_SCOPE, DATABASE_PORT +from constants import ALL_CLIENT_RELATIONS, APP_SCOPE, DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE from utils import new_password logger = logging.getLogger(__name__) - -ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( - "Please choose one endpoint to use. No need to relate all of them simultaneously!" -) - - class PostgreSQLProvider(Object): """Defines functionality for the 'provides' side of the 'postgresql-client' relation. @@ -201,7 +195,7 @@ def update_endpoints(self, event: DatabaseRequestedEvent = None) -> None: def _check_multiple_endpoints(self) -> bool: """Checks if there are relations with other endpoints.""" - relation_names = [relation.name for relation in self.charm.client_relations] + relation_names = set(relation.name for relation in self.charm.client_relations) if "database" in relation_names and len(relation_names) > 1: return True return False diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 1c13232835..2881870f6f 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -49,6 +49,7 @@ async def test_deploy_charms(ops_test: OpsTest, charm): MAILMAN3_CORE_APP_NAME, application_name=MAILMAN3_CORE_APP_NAME, channel="stable", + series="focal", config={"hostname": "example.org"}, ), ) From 138aa3d80d8c2ca758d2cd106b6b4ab7d5be64f5 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Fri, 22 Mar 2024 15:26:32 +0200 Subject: [PATCH 11/18] refactoring --- src/constants.py | 2 +- src/relations/db.py | 8 +++++++- src/relations/postgresql_provider.py | 10 ++++++++-- tests/integration/relations/test_relations.py | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/constants.py b/src/constants.py index 6af08bfafc..9fc95fca76 100644 --- a/src/constants.py +++ b/src/constants.py @@ -71,4 +71,4 @@ ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE = ( "Please choose one endpoint to use. No need to relate all of them simultaneously!" -) \ No newline at end of file +) diff --git a/src/relations/db.py b/src/relations/db.py index 50f5b37a7d..f16215ffe3 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -21,7 +21,12 @@ from ops.model import ActiveStatus, BlockedStatus, Relation, Unit from pgconnstr import ConnectionString -from constants import ALL_LEGACY_RELATIONS, APP_SCOPE, DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE +from constants import ( + ALL_LEGACY_RELATIONS, + APP_SCOPE, + DATABASE_PORT, + ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE, +) from utils import new_password logger = logging.getLogger(__name__) @@ -34,6 +39,7 @@ "roles requested through relation, use postgresql_client interface instead" ) + class DbProvides(Object): """Defines functionality for the 'provides' side of the 'db' relation. diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index cffc1bbdb5..29f2748f5a 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -22,11 +22,17 @@ from ops.framework import Object from ops.model import ActiveStatus, BlockedStatus, Relation -from constants import ALL_CLIENT_RELATIONS, APP_SCOPE, DATABASE_PORT, ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE +from constants import ( + ALL_CLIENT_RELATIONS, + APP_SCOPE, + DATABASE_PORT, + ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE, +) from utils import new_password logger = logging.getLogger(__name__) + class PostgreSQLProvider(Object): """Defines functionality for the 'provides' side of the 'postgresql-client' relation. @@ -195,7 +201,7 @@ def update_endpoints(self, event: DatabaseRequestedEvent = None) -> None: def _check_multiple_endpoints(self) -> bool: """Checks if there are relations with other endpoints.""" - relation_names = set(relation.name for relation in self.charm.client_relations) + relation_names = {relation.name for relation in self.charm.client_relations} if "database" in relation_names and len(relation_names) > 1: return True return False diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 2881870f6f..4617e57464 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -9,7 +9,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, stop_after_delay, wait_fixed -from ..helpers import CHARM_SERIES, DATABASE_APP_NAME, METADATA +from ..helpers import CHARM_SERIES, METADATA from ..new_relations.test_new_relations import APPLICATION_APP_NAME, build_connection_string from ..relations.helpers import get_legacy_db_connection_str From 06955dd30882489b8ef95458a466ada569bd5a9f Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Mon, 25 Mar 2024 17:58:57 +0200 Subject: [PATCH 12/18] update status on remove multiple relations endpoint --- src/relations/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relations/db.py b/src/relations/db.py index f16215ffe3..925339946a 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -308,7 +308,7 @@ def _update_unit_status_on_blocking_endpoint_simultaneously(self): self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE ): - if not self._check_multiple_endpoints(): + if not self._check_relation_another_endpoint(): self.charm.unit.status = ActiveStatus() def update_endpoints(self, relation: Relation = None) -> None: From 1b52b8cc258a7e96c3d74194b35f32ac15cc33fc Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Mon, 25 Mar 2024 18:01:19 +0200 Subject: [PATCH 13/18] update status on remove multiple relations endpoint --- src/relations/db.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/relations/db.py b/src/relations/db.py index 925339946a..d3944d76b3 100644 --- a/src/relations/db.py +++ b/src/relations/db.py @@ -98,7 +98,7 @@ def _check_exist_current_relation(self) -> bool: return True return False - def _check_relation_another_endpoint(self) -> bool: + def _check_multiple_endpoints(self) -> bool: """Checks if there are relations with other endpoints.""" is_exist = self._check_exist_current_relation() for relation in self.charm.client_relations: @@ -115,7 +115,7 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None: if not self.charm.unit.is_leader(): return - if self._check_relation_another_endpoint(): + if self._check_multiple_endpoints(): self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE) return @@ -308,7 +308,7 @@ def _update_unit_status_on_blocking_endpoint_simultaneously(self): self.charm.is_blocked and self.charm.unit.status.message == ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE ): - if not self._check_relation_another_endpoint(): + if not self._check_multiple_endpoints(): self.charm.unit.status = ActiveStatus() def update_endpoints(self, relation: Relation = None) -> None: From c0d39b82060ae9468f2fb40a192bf473d26aace2 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Fri, 5 Apr 2024 15:55:02 +0300 Subject: [PATCH 14/18] fix unit test. change db endpoint(mailman3_core) to db endpoint(postgresql_test_app) --- src/relations/postgresql_provider.py | 10 ---- tests/integration/relations/__init__.py | 2 +- tests/integration/relations/test_relations.py | 50 +++++++++++-------- tox.ini | 1 + 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/relations/postgresql_provider.py b/src/relations/postgresql_provider.py index 29f2748f5a..326ad27698 100644 --- a/src/relations/postgresql_provider.py +++ b/src/relations/postgresql_provider.py @@ -8,7 +8,6 @@ from charms.data_platform_libs.v0.data_interfaces import ( DatabaseProvides, DatabaseRequestedEvent, - diff, ) from charms.postgresql_k8s.v0.postgresql import ( INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE, @@ -226,15 +225,6 @@ def _on_relation_changed_event(self, event: RelationChangedEvent) -> None: if self._check_multiple_endpoints(): self.charm.unit.status = BlockedStatus(ENDPOINT_SIMULTANEOUSLY_BLOCKING_MESSAGE) return - # Check which data has changed to emit customs events. - _diff = diff(event, self.charm.unit) - - # Emit a database requested event if the setup key (database name and optional - # extra user roles) was added to the relation databag by the application. - if "database" in _diff.added: - getattr(self.database_provides.on, "database_requested").emit( - event.relation, app=event.app, unit=event.unit - ) def _update_unit_status_on_blocking_endpoint_simultaneously(self): """Clean up Blocked status if this is due related of multiple endpoints.""" diff --git a/tests/integration/relations/__init__.py b/tests/integration/relations/__init__.py index db3bfe1a65..e3979c0f63 100644 --- a/tests/integration/relations/__init__.py +++ b/tests/integration/relations/__init__.py @@ -1,2 +1,2 @@ -# Copyright 2023 Canonical Ltd. +# Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 4617e57464..05425439bf 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -16,11 +16,13 @@ logger = logging.getLogger(__name__) APP_NAME = METADATA["name"] -MAILMAN3_CORE_APP_NAME = "mailman3-core" +# MAILMAN3_CORE_APP_NAME = "mailman3-core" DB_RELATION = "db" DATABASE_RELATION = "database" FIRST_DATABASE_RELATION = "first-database" -APP_NAMES = [APP_NAME, APPLICATION_APP_NAME, MAILMAN3_CORE_APP_NAME] +DATABASE_APP_NAME = "database-app" +DB_APP_NAME = "db-app" +APP_NAMES = [APP_NAME, DATABASE_APP_NAME, DB_APP_NAME] @pytest.mark.group(1) @@ -33,7 +35,7 @@ async def test_deploy_charms(ops_test: OpsTest, charm): await asyncio.gather( ops_test.model.deploy( APPLICATION_APP_NAME, - application_name=APPLICATION_APP_NAME, + application_name=DATABASE_APP_NAME, num_units=1, series=CHARM_SERIES, channel="edge", @@ -43,14 +45,18 @@ async def test_deploy_charms(ops_test: OpsTest, charm): application_name=APP_NAME, num_units=1, series=CHARM_SERIES, - config={"profile": "testing"}, + config={ + "profile": "testing", + "plugin_unaccent_enable": "True", + "plugin_pg_trgm_enable": "True", + }, ), ops_test.model.deploy( - MAILMAN3_CORE_APP_NAME, - application_name=MAILMAN3_CORE_APP_NAME, - channel="stable", - series="focal", - config={"hostname": "example.org"}, + APPLICATION_APP_NAME, + application_name=DB_APP_NAME, + num_units=1, + series=CHARM_SERIES, + channel="edge", ), ) @@ -59,8 +65,8 @@ async def test_deploy_charms(ops_test: OpsTest, charm): @pytest.mark.group(1) async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): - await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") app = ops_test.model.applications[APP_NAME] await ops_test.model.block_until( @@ -70,7 +76,7 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest logger.info(" remove relation with modern endpoints") await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + f"{APP_NAME}:{DATABASE_RELATION}", f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}" ) async with ops_test.fast_forward(): await ops_test.model.wait_for_idle( @@ -80,7 +86,7 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest ) legacy_interface_connect = await get_legacy_db_connection_str( - ops_test, MAILMAN3_CORE_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" + ops_test, DB_APP_NAME, DB_RELATION, remote_unit_name=f"{APP_NAME}/0" ) logger.info(f" check connect to = {legacy_interface_connect}") for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): @@ -88,10 +94,10 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest with psycopg2.connect(legacy_interface_connect) as connection: assert connection.status == psycopg2.extensions.STATUS_READY - logger.info(" remove relation mailman3-core") + logger.info(f" remove relation {DB_APP_NAME}:{DB_RELATION}") async with ops_test.fast_forward(): await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DB_RELATION}", f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}" + f"{APP_NAME}:{DB_RELATION}", f"{DB_APP_NAME}:{DB_RELATION}" ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) with pytest.raises(psycopg2.OperationalError): @@ -99,8 +105,8 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): - await ops_test.model.relate(MAILMAN3_CORE_APP_NAME, f"{APP_NAME}:{DB_RELATION}") - await ops_test.model.relate(APP_NAME, f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}") + await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") + await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") app = ops_test.model.applications[APP_NAME] await ops_test.model.block_until( @@ -110,24 +116,24 @@ async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest logger.info(" remove relation with legacy endpoints") await ops_test.model.applications[APP_NAME].remove_relation( - f"{MAILMAN3_CORE_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" + f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}" ) async with ops_test.fast_forward(): await ops_test.model.wait_for_idle(status="active", timeout=3000, raise_on_error=False) modern_interface_connect = await build_connection_string( - ops_test, APPLICATION_APP_NAME, FIRST_DATABASE_RELATION + ops_test, DATABASE_APP_NAME, FIRST_DATABASE_RELATION ) - logger.info(f" check connect to = {modern_interface_connect}") + logger.info(f"check connect to = {modern_interface_connect}") for attempt in Retrying(stop=stop_after_delay(60 * 3), wait=wait_fixed(10)): with attempt: with psycopg2.connect(modern_interface_connect) as connection: assert connection.status == psycopg2.extensions.STATUS_READY - logger.info(f" remove relation {APPLICATION_APP_NAME}") + logger.info(f"remove relation {DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") async with ops_test.fast_forward(): await ops_test.model.applications[APP_NAME].remove_relation( - f"{APP_NAME}:{DATABASE_RELATION}", f"{APPLICATION_APP_NAME}:{FIRST_DATABASE_RELATION}" + f"{APP_NAME}:{DATABASE_RELATION}", f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}" ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): diff --git a/tox.ini b/tox.ini index 18edb1846a..3230fdae90 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,7 @@ allowlist_externals = charmcraft charmcraftcache mv + psycopg2-binary commands_pre = poetry export --only main,charm-libs --output requirements.txt commands = From e0f3ddbb4aa988d6d9404837fda0e290a39db4ef Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 10 Apr 2024 14:37:41 +0300 Subject: [PATCH 15/18] add pytest.mark.group to test_modern_endpoint_with_multiple_related_endpoints --- tests/integration/relations/test_relations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index 05425439bf..afe4b68f65 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -104,6 +104,7 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest psycopg2.connect(legacy_interface_connect) +@pytest.mark.group(1) async def test_modern_endpoint_with_multiple_related_endpoints(ops_test: OpsTest): await ops_test.model.relate(f"{DB_APP_NAME}:{DB_RELATION}", f"{APP_NAME}:{DB_RELATION}") await ops_test.model.relate(APP_NAME, f"{DATABASE_APP_NAME}:{FIRST_DATABASE_RELATION}") From 530fe07b94c4db4152ee307dde0afc4578bd2659 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 10 Apr 2024 17:48:40 +0300 Subject: [PATCH 16/18] fix check connect to legacy endpoint --- tests/integration/relations/test_relations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/integration/relations/test_relations.py b/tests/integration/relations/test_relations.py index afe4b68f65..58a0462ceb 100644 --- a/tests/integration/relations/test_relations.py +++ b/tests/integration/relations/test_relations.py @@ -100,8 +100,10 @@ async def test_legacy_endpoint_with_multiple_related_endpoints(ops_test: OpsTest f"{APP_NAME}:{DB_RELATION}", f"{DB_APP_NAME}:{DB_RELATION}" ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) - with pytest.raises(psycopg2.OperationalError): - psycopg2.connect(legacy_interface_connect) + for attempt in Retrying(stop=stop_after_delay(60 * 5), wait=wait_fixed(10)): + with attempt: + with pytest.raises(psycopg2.OperationalError): + psycopg2.connect(legacy_interface_connect) @pytest.mark.group(1) From b04fe1610abf00db980fd0f66fbd9db3679d92e1 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Tue, 16 Apr 2024 22:02:21 +0300 Subject: [PATCH 17/18] test_self_healing: deploying postgresql-test-app revision 101 --- tests/integration/ha_tests/test_self_healing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 63d5b5abaa..75e84ae804 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -86,6 +86,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: APPLICATION_NAME, application_name=APPLICATION_NAME, series=CHARM_SERIES, + revision=101, channel="edge", ) From 9aa93db8e101f61ef37ff5de4f0d12f4a7015d13 Mon Sep 17 00:00:00 2001 From: DmitriyBalaba Date: Wed, 17 Apr 2024 16:49:59 +0300 Subject: [PATCH 18/18] deploy postgresql-test-app with latest version --- tests/integration/ha_tests/test_self_healing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 75e84ae804..63d5b5abaa 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -86,7 +86,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: APPLICATION_NAME, application_name=APPLICATION_NAME, series=CHARM_SERIES, - revision=101, channel="edge", )