diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5865d3f195..07f06f160c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,7 +54,7 @@ jobs: - charm-integration - database-relation-integration - db-relation-integration - - db-admin-relation-integration +# - db-admin-relation-integration - ha-self-healing-integration - password-rotation-integration - tls-integration diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4b3ea5f156..f890dec6e0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: needs: - lib-check - ci-tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e72d3264c..309d38f27f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,5 +55,5 @@ juju add-model dev # Enable DEBUG logging juju model-config logging-config="=INFO;unit=DEBUG" # Deploy the charm -juju deploy ./postgresql_ubuntu-20.04-amd64.charm +juju deploy ./postgresql_ubuntu-22.04-amd64.charm ``` \ No newline at end of file diff --git a/charmcraft.yaml b/charmcraft.yaml index 3c4abf5ef4..3567817010 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -5,10 +5,10 @@ type: charm bases: - build-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" run-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" parts: charm: build-packages: diff --git a/metadata.yaml b/metadata.yaml index a58452ab49..7a5139201a 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -7,11 +7,6 @@ description: | summary: | Charm to operate the PostgreSQL database on machines -series: - # TODO: add jammy when it's released - - focal - - bionic - peers: database-peers: interface: postgresql_peers diff --git a/tests/integration/ha_tests/application-charm/charmcraft.yaml b/tests/integration/ha_tests/application-charm/charmcraft.yaml index 5f5c2b50f0..a136cbf478 100644 --- a/tests/integration/ha_tests/application-charm/charmcraft.yaml +++ b/tests/integration/ha_tests/application-charm/charmcraft.yaml @@ -5,10 +5,10 @@ type: charm bases: - build-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" run-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" parts: charm: charm-binary-python-packages: [psycopg2-binary==2.9.3] diff --git a/tests/integration/ha_tests/conftest.py b/tests/integration/ha_tests/conftest.py index 593e97b237..2e8a87ecaf 100644 --- a/tests/integration/ha_tests/conftest.py +++ b/tests/integration/ha_tests/conftest.py @@ -13,7 +13,7 @@ get_postgresql_parameter, update_restart_delay, ) -from tests.integration.helpers import run_command_on_unit +from tests.integration.helpers import CHARM_SERIES, run_command_on_unit APPLICATION_NAME = "application" @@ -25,7 +25,9 @@ async def continuous_writes(ops_test: OpsTest) -> None: async with ops_test.fast_forward(): if await app_name(ops_test, APPLICATION_NAME) is None: charm = await ops_test.build_charm("tests/integration/ha_tests/application-charm") - await ops_test.model.deploy(charm, application_name=APPLICATION_NAME) + await ops_test.model.deploy( + charm, application_name=APPLICATION_NAME, series=CHARM_SERIES + ) await ops_test.model.wait_for_idle(status="active", timeout=1000) yield # Clear the written data at the end. diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 26c5598b69..e9a7b263bc 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -28,6 +28,7 @@ update_restart_delay, ) from tests.integration.helpers import ( + CHARM_SERIES, db_connect, get_password, get_unit_address, @@ -50,7 +51,9 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): - await ops_test.model.deploy(charm, resources={"patroni": "patroni.tar.gz"}, num_units=3) + await ops_test.model.deploy( + charm, resources={"patroni": "patroni.tar.gz"}, num_units=3, series=CHARM_SERIES + ) await ops_test.juju("attach-resource", APP_NAME, "patroni=patroni.tar.gz") await ops_test.model.wait_for_idle(status="active", timeout=1000) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 1cd9f61f16..3ff00a1b79 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -27,6 +27,7 @@ wait_fixed, ) +CHARM_SERIES = "jammy" METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) DATABASE_APP_NAME = METADATA["name"] @@ -183,11 +184,6 @@ def check_patroni(ops_test: OpsTest, unit_name: str, restart_time: float) -> boo return postmaster_start_time > restart_time and health_info["state"] == "running" -def build_application_name(series: str) -> str: - """Return a composite application name combining application name and series.""" - return f"{DATABASE_APP_NAME}-{series}" - - async def check_cluster_members(ops_test: OpsTest, application_name: str) -> None: """Check that the correct members are part of the cluster. diff --git a/tests/integration/new_relations/application-charm/charmcraft.yaml b/tests/integration/new_relations/application-charm/charmcraft.yaml index e109b8b2dd..d37ba37eac 100644 --- a/tests/integration/new_relations/application-charm/charmcraft.yaml +++ b/tests/integration/new_relations/application-charm/charmcraft.yaml @@ -5,7 +5,7 @@ type: charm bases: - build-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" run-on: - name: "ubuntu" - channel: "20.04" + channel: "22.04" diff --git a/tests/integration/new_relations/test_new_relations.py b/tests/integration/new_relations/test_new_relations.py index b9ca63b13b..ea423fc687 100644 --- a/tests/integration/new_relations/test_new_relations.py +++ b/tests/integration/new_relations/test_new_relations.py @@ -10,7 +10,7 @@ import yaml from pytest_operator.plugin import OpsTest -from tests.integration.helpers import scale_application +from tests.integration.helpers import CHARM_SERIES, scale_application from tests.integration.new_relations.helpers import ( build_connection_string, check_relation_data_existence, @@ -42,20 +42,21 @@ async def test_deploy_charms(ops_test: OpsTest, application_charm, database_char application_charm, application_name=APPLICATION_APP_NAME, num_units=2, + series=CHARM_SERIES, ), ops_test.model.deploy( database_charm, resources={"patroni": "patroni.tar.gz"}, application_name=DATABASE_APP_NAME, num_units=1, - trust=True, + series=CHARM_SERIES, ), ops_test.model.deploy( database_charm, resources={"patroni": "patroni.tar.gz"}, application_name=ANOTHER_DATABASE_APP_NAME, num_units=2, - trust=True, + series=CHARM_SERIES, ), ) diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 1ce8487c67..453af7b7fe 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -14,8 +14,8 @@ from tests.helpers import STORAGE_PATH from tests.integration.helpers import ( + CHARM_SERIES, DATABASE_APP_NAME, - build_application_name, check_cluster_members, convert_records_to_dict, db_connect, @@ -29,63 +29,54 @@ logger = logging.getLogger(__name__) -SERIES = ["focal"] UNIT_IDS = [0, 1, 2] @pytest.mark.abort_on_fail -@pytest.mark.parametrize("series", SERIES) @pytest.mark.skip_if_deployed -async def test_deploy(ops_test: OpsTest, charm: str, series: str): +async def test_deploy(ops_test: OpsTest, charm: str): """Deploy the charm-under-test. Assert on the unit status before any relations/configurations take place. """ - # Set a composite application name in order to test in more than one series at the same time. - application_name = build_application_name(series) - # Deploy the charm with Patroni resource. resources = {"patroni": "patroni.tar.gz"} await ops_test.model.deploy( - charm, resources=resources, application_name=application_name, series=series, num_units=3 + charm, + resources=resources, + application_name=DATABASE_APP_NAME, + num_units=3, + series=CHARM_SERIES, ) # Attach the resource to the controller. - await ops_test.juju("attach-resource", application_name, "patroni=patroni.tar.gz") + await ops_test.juju("attach-resource", DATABASE_APP_NAME, "patroni=patroni.tar.gz") # Reducing the update status frequency to speed up the triggering of deferred events. await ops_test.model.set_config({"update-status-hook-interval": "10s"}) - await ops_test.model.wait_for_idle(apps=[application_name], status="active", timeout=1000) - assert ops_test.model.applications[application_name].units[0].workload_status == "active" + await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000) + assert ops_test.model.applications[DATABASE_APP_NAME].units[0].workload_status == "active" @pytest.mark.abort_on_fail -@pytest.mark.parametrize("series", SERIES) @pytest.mark.parametrize("unit_id", UNIT_IDS) -async def test_database_is_up(ops_test: OpsTest, series: str, unit_id: int): - # Set a composite application name in order to test in more than one series at the same time. - application_name = build_application_name(series) - +async def test_database_is_up(ops_test: OpsTest, unit_id: int): # Query Patroni REST API and check the status that indicates # both Patroni and PostgreSQL are up and running. - host = get_unit_address(ops_test, f"{application_name}/{unit_id}") + host = get_unit_address(ops_test, f"{DATABASE_APP_NAME}/{unit_id}") result = requests.get(f"http://{host}:8008/health") assert result.status_code == 200 -@pytest.mark.parametrize("series", SERIES) @pytest.mark.parametrize("unit_id", UNIT_IDS) -async def test_settings_are_correct(ops_test: OpsTest, series: str, unit_id: int): +async def test_settings_are_correct(ops_test: OpsTest, unit_id: int): # Connect to the PostgreSQL instance. - # Set a composite application name in order to test in more than one series at the same time. - application_name = build_application_name(series) - # Retrieving the operator user password using the action. - any_unit_name = ops_test.model.applications[application_name].units[0].name + any_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name password = await get_password(ops_test, any_unit_name) # Connect to PostgreSQL. - host = get_unit_address(ops_test, f"{application_name}/{unit_id}") + host = get_unit_address(ops_test, f"{DATABASE_APP_NAME}/{unit_id}") logger.info("connecting to the database host: %s", host) with db_connect(host, password) as connection: assert connection.status == psycopg2.extensions.STATUS_READY @@ -117,7 +108,7 @@ async def test_settings_are_correct(ops_test: OpsTest, series: str, unit_id: int # Validate each configuration set by Patroni on PostgreSQL. assert settings["archive_command"] == "/bin/true" assert settings["archive_mode"] == "on" - assert settings["cluster_name"] == f"{DATABASE_APP_NAME}-{series}" + assert settings["cluster_name"] == DATABASE_APP_NAME assert settings["data_directory"] == f"{STORAGE_PATH}/pgdata" assert settings["data_checksums"] == "on" assert settings["listen_addresses"] == host @@ -136,33 +127,29 @@ async def test_settings_are_correct(ops_test: OpsTest, series: str, unit_id: int assert settings["maximum_lag_on_failover"] == 1048576 -@pytest.mark.parametrize("series", SERIES) -async def test_scale_down_and_up(ops_test: OpsTest, series: str): +async def test_scale_down_and_up(ops_test: OpsTest): """Test data is replicated to new units after a scale up.""" - # Set a composite application name in order to test in more than one series at the same time. - application_name = build_application_name(series) - # Ensure the initial number of units in the application. initial_scale = len(UNIT_IDS) - await scale_application(ops_test, application_name, initial_scale) + await scale_application(ops_test, DATABASE_APP_NAME, initial_scale) # Scale down the application. - await scale_application(ops_test, application_name, initial_scale - 1) + await scale_application(ops_test, DATABASE_APP_NAME, initial_scale - 1) # Ensure the member was correctly removed from the cluster # (by comparing the cluster members and the current units). - await check_cluster_members(ops_test, application_name) + await check_cluster_members(ops_test, DATABASE_APP_NAME) # Scale up the application (2 more units than the current scale). - await scale_application(ops_test, application_name, initial_scale + 1) + await scale_application(ops_test, DATABASE_APP_NAME, initial_scale + 1) # Assert the correct members are part of the cluster. - await check_cluster_members(ops_test, application_name) + await check_cluster_members(ops_test, DATABASE_APP_NAME) # Test the deletion of the unit that is both the leader and the primary. - any_unit_name = ops_test.model.applications[application_name].units[0].name + any_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name primary = await get_primary(ops_test, any_unit_name) - leader_unit = await find_unit(ops_test, leader=True, application=application_name) + leader_unit = await find_unit(ops_test, leader=True, application=DATABASE_APP_NAME) # Trigger a switchover if the primary and the leader are not the same unit. if primary != leader_unit.name: @@ -177,21 +164,21 @@ async def test_scale_down_and_up(ops_test: OpsTest, series: str): with attempt: assert primary == leader_unit.name - await ops_test.model.applications[application_name].destroy_units(leader_unit.name) + await ops_test.model.applications[DATABASE_APP_NAME].destroy_units(leader_unit.name) await ops_test.model.wait_for_idle( - apps=[application_name], status="active", timeout=1000, wait_for_exact_units=initial_scale + apps=[DATABASE_APP_NAME], status="active", timeout=1000, wait_for_exact_units=initial_scale ) # Assert the correct members are part of the cluster. - await check_cluster_members(ops_test, application_name) + await check_cluster_members(ops_test, DATABASE_APP_NAME) # Scale up the application (2 more units than the current scale). - await scale_application(ops_test, application_name, initial_scale + 2) + await scale_application(ops_test, DATABASE_APP_NAME, initial_scale + 2) # Test the deletion of both the unit that is the leader and the unit that is the primary. - any_unit_name = ops_test.model.applications[application_name].units[0].name + any_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name primary = await get_primary(ops_test, any_unit_name) - leader_unit = await find_unit(ops_test, application_name, True) + leader_unit = await find_unit(ops_test, DATABASE_APP_NAME, True) # Trigger a switchover if the primary and the leader are the same unit. if primary == leader_unit.name: @@ -206,27 +193,25 @@ async def test_scale_down_and_up(ops_test: OpsTest, series: str): with attempt: assert primary != leader_unit.name - await ops_test.model.applications[application_name].destroy_units(primary, leader_unit.name) + await ops_test.model.applications[DATABASE_APP_NAME].destroy_units(primary, leader_unit.name) await ops_test.model.wait_for_idle( - apps=[application_name], + apps=[DATABASE_APP_NAME], status="active", timeout=1000, wait_for_exact_units=initial_scale, ) # Assert the correct members are part of the cluster. - await check_cluster_members(ops_test, application_name) + await check_cluster_members(ops_test, DATABASE_APP_NAME) # End with the cluster having the initial number of units. - await scale_application(ops_test, application_name, initial_scale) + await scale_application(ops_test, DATABASE_APP_NAME, initial_scale) -@pytest.mark.parametrize("series", SERIES) -async def test_persist_data_through_primary_deletion(ops_test: OpsTest, series: str): +async def test_persist_data_through_primary_deletion(ops_test: OpsTest): """Test data persists through a primary deletion.""" # Set a composite application name in order to test in more than one series at the same time. - application_name = build_application_name(series) - any_unit_name = ops_test.model.applications[application_name].units[0].name + any_unit_name = ops_test.model.applications[DATABASE_APP_NAME].units[0].name primary = await get_primary(ops_test, any_unit_name) password = await get_password(ops_test, primary) @@ -243,14 +228,14 @@ async def test_persist_data_through_primary_deletion(ops_test: OpsTest, series: await ops_test.model.destroy_units( primary, ) - await ops_test.model.wait_for_idle(apps=[application_name], status="active", timeout=1000) + await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000) # Add the unit again. - await ops_test.model.applications[application_name].add_unit(count=1) - await ops_test.model.wait_for_idle(apps=[application_name], status="active", timeout=1000) + await ops_test.model.applications[DATABASE_APP_NAME].add_unit(count=1) + await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000) # Testing write occurred to every postgres instance by reading from them - for unit in ops_test.model.applications[application_name].units: + for unit in ops_test.model.applications[DATABASE_APP_NAME].units: host = unit.public_address logger.info("connecting to the database host: %s", host) with db_connect(host, password) as connection: diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 89f960c17d..4ccf3cdc8b 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -10,6 +10,7 @@ from pytest_operator.plugin import OpsTest from tests.integration.helpers import ( + CHARM_SERIES, DATABASE_APP_NAME, build_connection_string, check_database_users_existence, @@ -35,6 +36,7 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: resources=resources, application_name=DATABASE_APP_NAME, num_units=DATABASE_UNITS, + series=CHARM_SERIES, ) # Attach the resource to the controller. await ops_test.juju("attach-resource", DATABASE_APP_NAME, "patroni=patroni.tar.gz") diff --git a/tests/integration/test_db_admin.py b/tests/integration/test_db_admin.py index c3595b890e..1d2eec21c3 100644 --- a/tests/integration/test_db_admin.py +++ b/tests/integration/test_db_admin.py @@ -9,6 +9,7 @@ from pytest_operator.plugin import OpsTest from tests.integration.helpers import ( + CHARM_SERIES, DATABASE_APP_NAME, check_database_users_existence, check_databases_creation, @@ -44,6 +45,7 @@ async def test_landscape_scalable_bundle_db(ops_test: OpsTest, charm: str) -> No resources=resources, application_name=DATABASE_APP_NAME, num_units=DATABASE_UNITS, + series=CHARM_SERIES, ) # Attach the resource to the controller. await ops_test.juju("attach-resource", DATABASE_APP_NAME, "patroni=patroni.tar.gz") diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index c3f55fc2e8..eb332f0a30 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -8,6 +8,7 @@ from tests.helpers import METADATA from tests.integration.helpers import ( + CHARM_SERIES, check_patroni, get_password, restart_patroni, @@ -24,7 +25,11 @@ async def test_deploy_active(ops_test: OpsTest): charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( - charm, resources={"patroni": "patroni.tar.gz"}, application_name=APP_NAME, num_units=3 + charm, + resources={"patroni": "patroni.tar.gz"}, + application_name=APP_NAME, + num_units=3, + series=CHARM_SERIES, ) await ops_test.juju("attach-resource", APP_NAME, "patroni=patroni.tar.gz") await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index ccb23f74a9..c172f22a4f 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -10,6 +10,7 @@ from tests.helpers import METADATA from tests.integration.helpers import ( + CHARM_SERIES, DATABASE_APP_NAME, change_master_start_timeout, check_tls, @@ -37,7 +38,11 @@ async def test_deploy_active(ops_test: OpsTest): charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( - charm, resources={"patroni": "patroni.tar.gz"}, application_name=APP_NAME, num_units=3 + charm, + resources={"patroni": "patroni.tar.gz"}, + application_name=APP_NAME, + num_units=3, + series=CHARM_SERIES, ) await ops_test.juju("attach-resource", APP_NAME, "patroni=patroni.tar.gz") # No wait between deploying charms, since we can't guarantee users will wait. Furthermore, @@ -84,7 +89,7 @@ async def test_tls_enabled(ops_test: OpsTest) -> None: await run_command_on_unit( ops_test, replica, - "su -c '/usr/lib/postgresql/12/bin/pg_ctl -D /var/lib/postgresql/data/pgdata promote' postgres", + "su -c '/usr/lib/postgresql/14/bin/pg_ctl -D /var/lib/postgresql/data/pgdata promote' postgres", ) # Check that the replica was promoted. @@ -124,8 +129,7 @@ async def test_tls_enabled(ops_test: OpsTest) -> None: primary = await get_primary(ops_test, primary) logs = await run_command_on_unit(ops_test, primary, "journalctl -u patroni.service") assert ( - "connection authorized: user=rewind database=postgres SSL enabled" - " (protocol=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384, bits=256, compression=off)" in logs + "connection authorized: user=rewind database=postgres SSL enabled" in logs ), "TLS is not being used on pg_rewind connections" # Remove the relation.