diff --git a/lib/charms/data_platform_libs/v0/upgrade.py b/lib/charms/data_platform_libs/v0/upgrade.py index b8c753776b..670f4652e5 100644 --- a/lib/charms/data_platform_libs/v0/upgrade.py +++ b/lib/charms/data_platform_libs/v0/upgrade.py @@ -285,7 +285,7 @@ def restart(self, event) -> None: # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 13 +LIBPATCH = 14 PYDEPS = ["pydantic>=1.10,<2", "poetry-core"] @@ -895,6 +895,10 @@ def _on_upgrade_charm(self, event: UpgradeCharmEvent) -> None: self.charm.unit.status = WaitingStatus("other units upgrading first...") self.peer_relation.data[self.charm.unit].update({"state": "ready"}) + if self.charm.app.planned_units() == 1: + # single unit upgrade, emit upgrade_granted event right away + getattr(self.on, "upgrade_granted").emit() + else: # for k8s run version checks only on highest ordinal unit if ( diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index 7df406de0f..bfda780e8c 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -19,7 +19,7 @@ Any charm using this library should import the `psycopg2` or `psycopg2-binary` dependency. """ import logging -from typing import List, Optional, Set, Tuple +from typing import Dict, List, Optional, Set, Tuple import psycopg2 from psycopg2 import sql @@ -32,7 +32,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 15 +LIBPATCH = 16 INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" @@ -441,7 +441,7 @@ def is_restart_pending(self) -> bool: @staticmethod def build_postgresql_parameters( profile: str, available_memory: int, limit_memory: Optional[int] = None - ) -> Optional[dict[str, str]]: + ) -> Optional[Dict[str, str]]: """Builds the PostgreSQL parameters. Args: diff --git a/renovate.json b/renovate.json index 8637ac0a1f..ebe0525865 100644 --- a/renovate.json +++ b/renovate.json @@ -41,9 +41,6 @@ "matchDatasources": ["pypi"], "allowedVersions": "<3.0.0", "groupName": "Juju 2" - }, { - "matchPackageNames": ["urllib3"], - "allowedVersions": "<2.0.0" }, { "matchPackageNames": ["pydantic"], "allowedVersions": "<2.0.0" diff --git a/src/charm.py b/src/charm.py index 7eb32c6931..6488d08e20 100755 --- a/src/charm.py +++ b/src/charm.py @@ -101,7 +101,12 @@ def __init__(self, *args): self.secrets = {APP_SCOPE: {}, UNIT_SCOPE: {}} - self._observer = ClusterTopologyObserver(self) + juju_version = JujuVersion.from_environ() + if juju_version.major > 2: + run_cmd = "/usr/bin/juju-exec" + else: + run_cmd = "/usr/bin/juju-run" + self._observer = ClusterTopologyObserver(self, run_cmd) self.framework.observe(self.on.cluster_topology_change, self._on_cluster_topology_change) self.framework.observe(self.on.install, self._on_install) self.framework.observe(self.on.leader_elected, self._on_leader_elected) diff --git a/src/cluster_topology_observer.py b/src/cluster_topology_observer.py index 6d529bbdb8..31fd591e4a 100644 --- a/src/cluster_topology_observer.py +++ b/src/cluster_topology_observer.py @@ -42,15 +42,17 @@ class ClusterTopologyObserver(Object): Observed cluster topology changes cause :class"`ClusterTopologyChangeEvent` to be emitted. """ - def __init__(self, charm: CharmBase): + def __init__(self, charm: CharmBase, run_cmd: str): """Constructor for ClusterTopologyObserver. Args: charm: the charm that is instantiating the library. + run_cmd: run command to use to dispatch events. """ super().__init__(charm, "cluster-topology-observer") self._charm = charm + self._run_cmd = run_cmd def start_observer(self): """Start the cluster topology observer running in a new process.""" @@ -79,7 +81,7 @@ def start_observer(self): "src/cluster_topology_observer.py", self._charm._patroni._patroni_url, f"{self._charm._patroni.verify}", - "/usr/bin/juju-run", + self._run_cmd, self._charm.unit.name, self._charm.charm_dir, ], diff --git a/src/grafana_dashboards/postgresql-metrics.json b/src/grafana_dashboards/postgresql-metrics.json index c6595bb8f1..83732b0756 100644 --- a/src/grafana_dashboards/postgresql-metrics.json +++ b/src/grafana_dashboards/postgresql-metrics.json @@ -2667,35 +2667,35 @@ "steppedLine": false, "targets": [ { - "expr": "irate(pg_stat_bgwriter_buffers_backend{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_buffers_backend_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "buffers_backend", "refId": "A" }, { - "expr": "irate(pg_stat_bgwriter_buffers_alloc{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_buffers_alloc_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "buffers_alloc", "refId": "B" }, { - "expr": "irate(pg_stat_bgwriter_buffers_backend_fsync{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_buffers_backend_fsync_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "backend_fsync", "refId": "C" }, { - "expr": "irate(pg_stat_bgwriter_buffers_checkpoint{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_buffers_checkpoint_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "buffers_checkpoint", "refId": "D" }, { - "expr": "irate(pg_stat_bgwriter_buffers_clean{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_buffers_clean_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "buffers_clean", @@ -2973,14 +2973,14 @@ "steppedLine": false, "targets": [ { - "expr": "irate(pg_stat_bgwriter_checkpoint_write_time{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_checkpoint_write_time_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "write_time - Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk.", "refId": "B" }, { - "expr": "irate(pg_stat_bgwriter_checkpoint_sync_time{instance=\"$instance\"}[5m])", + "expr": "irate(pg_stat_bgwriter_checkpoint_sync_time_total{instance=\"$instance\"}[5m])", "format": "time_series", "intervalFactor": 1, "legendFormat": "sync_time - Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk.", diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 2e4ec2913f..b767e613c5 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1423,3 +1423,19 @@ def test_get_available_memory(self): with patch("builtins.open", mock_open(read_data="")): self.assertEqual(self.charm.get_available_memory(), 0) + + @patch("charm.ClusterTopologyObserver") + @patch("charm.JujuVersion") + def test_juju_run_exec_divergence(self, _juju_version: Mock, _topology_observer: Mock): + # Juju 2 + _juju_version.from_environ.return_value.major = 2 + harness = Harness(PostgresqlOperatorCharm) + harness.begin() + _topology_observer.assert_called_once_with(harness.charm, "/usr/bin/juju-run") + _topology_observer.reset_mock() + + # Juju 3 + _juju_version.from_environ.return_value.major = 3 + harness = Harness(PostgresqlOperatorCharm) + harness.begin() + _topology_observer.assert_called_once_with(harness.charm, "/usr/bin/juju-exec") diff --git a/tests/unit/test_cluster_topology_observer.py b/tests/unit/test_cluster_topology_observer.py index fc405d8256..03fce108ce 100644 --- a/tests/unit/test_cluster_topology_observer.py +++ b/tests/unit/test_cluster_topology_observer.py @@ -41,7 +41,7 @@ class MockCharm(CharmBase): def __init__(self, *args): super().__init__(*args) - self.observer = ClusterTopologyObserver(self) + self.observer = ClusterTopologyObserver(self, "test-command") self.framework.observe(self.on.cluster_topology_change, self._on_cluster_topology_change) def _on_cluster_topology_change(self, _) -> None: