Skip to content

Commit c672b6e

Browse files
authored
Toggle plugins in one go (#279)
1 parent 479cbd3 commit c672b6e

File tree

6 files changed

+26
-42
lines changed

6 files changed

+26
-42
lines changed

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
# Increment this PATCH version before using `charmcraft publish-lib` or reset
3434
# to 0 if you are raising the major API version
35-
LIBPATCH = 19
35+
LIBPATCH = 20
3636

3737
INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles"
3838

@@ -172,8 +172,7 @@ def create_database(self, database: str, user: str, plugins: List[str] = []) ->
172172
raise PostgreSQLCreateDatabaseError()
173173

174174
# Enable preset extensions
175-
for plugin in plugins:
176-
self.enable_disable_extension(plugin, True, database)
175+
self.enable_disable_extensions({plugin: True for plugin in plugins}, database)
177176

178177
def create_user(
179178
self, user: str, password: str = None, admin: bool = False, extra_user_roles: str = None
@@ -270,22 +269,16 @@ def delete_user(self, user: str) -> None:
270269
logger.error(f"Failed to delete user: {e}")
271270
raise PostgreSQLDeleteUserError()
272271

273-
def enable_disable_extension(self, extension: str, enable: bool, database: str = None) -> None:
272+
def enable_disable_extensions(self, extensions: Dict[str, bool], database: str = None) -> None:
274273
"""Enables or disables a PostgreSQL extension.
275274
276275
Args:
277-
extension: the name of the extensions.
278-
enable: whether the extension should be enabled or disabled.
276+
extensions: the name of the extensions.
279277
database: optional database where to enable/disable the extension.
280278
281279
Raises:
282280
PostgreSQLEnableDisableExtensionError if the operation fails.
283281
"""
284-
statement = (
285-
f"CREATE EXTENSION IF NOT EXISTS {extension};"
286-
if enable
287-
else f"DROP EXTENSION IF EXISTS {extension};"
288-
)
289282
connection = None
290283
try:
291284
if database is not None:
@@ -301,7 +294,12 @@ def enable_disable_extension(self, extension: str, enable: bool, database: str =
301294
with self._connect_to_database(
302295
database=database
303296
) as connection, connection.cursor() as cursor:
304-
cursor.execute(statement)
297+
for extension, enable in extensions.items():
298+
cursor.execute(
299+
f"CREATE EXTENSION IF NOT EXISTS {extension};"
300+
if enable
301+
else f"DROP EXTENSION IF EXISTS {extension};"
302+
)
305303
except psycopg2.errors.UniqueViolation:
306304
pass
307305
except psycopg2.Error:

src/charm.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -948,21 +948,20 @@ def enable_disable_extensions(self, database: str = None) -> None:
948948
database: optional database where to enable/disable the extension.
949949
"""
950950
original_status = self.unit.status
951+
extensions = {}
952+
# collect extensions
951953
for plugin in self.config.plugin_keys():
952954
enable = self.config[plugin]
953955

954956
# Enable or disable the plugin/extension.
955957
extension = "_".join(plugin.split("_")[1:-1])
956-
self.unit.status = WaitingStatus(
957-
f"{'Enabling' if enable else 'Disabling'} {extension}"
958-
)
959-
try:
960-
self.postgresql.enable_disable_extension(extension, enable, database)
961-
except PostgreSQLEnableDisableExtensionError as e:
962-
logger.exception(
963-
f"failed to {'enable' if enable else 'disable'} {extension} plugin: %s", str(e)
964-
)
965-
self.unit.status = original_status
958+
extensions[extension] = enable
959+
self.unit.status = WaitingStatus("Updating extensions")
960+
try:
961+
self.postgresql.enable_disable_extensions(extensions, database)
962+
except PostgreSQLEnableDisableExtensionError as e:
963+
logger.exception("failed to change plugins: %s", str(e))
964+
self.unit.status = original_status
966965

967966
def _get_ips_to_remove(self) -> Set[str]:
968967
"""List the IPs that were part of the cluster but departed."""

src/relations/db.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ def set_up_relation(self, relation: Relation) -> bool:
176176

177177
self.charm.postgresql.create_database(database, user, plugins=plugins)
178178

179-
# Enable/disable extensions in the new database.
180-
self.charm.enable_disable_extensions(database)
181179
except (PostgreSQLCreateDatabaseError, PostgreSQLCreateUserError) as e:
182180
logger.exception(e)
183181
self.charm.unit.status = BlockedStatus(

tests/integration/test_plugins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async def test_plugins(ops_test: OpsTest) -> None:
3838
series=CHARM_SERIES,
3939
config={"profile": "testing"},
4040
)
41-
await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active")
41+
await ops_test.model.wait_for_idle(apps=[DATABASE_APP_NAME], status="active", timeout=1000)
4242

4343
# Check that the available plugins are disabled.
4444
primary = await get_primary(ops_test, f"{DATABASE_APP_NAME}/0")

tests/unit/test_charm.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,22 +288,22 @@ def test_enable_disable_extensions(self, _):
288288
postgresql_mock.enable_disable_extension.side_effect = None
289289
with self.assertNoLogs("charm", "ERROR"):
290290
self.charm.enable_disable_extensions()
291-
self.assertEqual(postgresql_mock.enable_disable_extension.call_count, 6)
291+
self.assertEqual(postgresql_mock.enable_disable_extensions.call_count, 1)
292292

293293
# Test when one extension install/uninstall fails.
294294
postgresql_mock.reset_mock()
295-
postgresql_mock.enable_disable_extension.side_effect = (
295+
postgresql_mock.enable_disable_extensions.side_effect = (
296296
PostgreSQLEnableDisableExtensionError
297297
)
298298
with self.assertLogs("charm", "ERROR") as logs:
299299
self.charm.enable_disable_extensions()
300-
self.assertEqual(postgresql_mock.enable_disable_extension.call_count, 6)
301-
self.assertIn("failed to disable citext plugin", "".join(logs.output))
300+
self.assertEqual(postgresql_mock.enable_disable_extensions.call_count, 1)
301+
self.assertIn("failed to change plugins", "".join(logs.output))
302302

303303
# Test when one config option should be skipped (because it's not related
304304
# to a plugin/extension).
305305
postgresql_mock.reset_mock()
306-
postgresql_mock.enable_disable_extension.side_effect = None
306+
postgresql_mock.enable_disable_extensions.side_effect = None
307307
with self.assertNoLogs("charm", "ERROR"):
308308
config = """options:
309309
plugin_citext_enable:
@@ -331,7 +331,7 @@ def test_enable_disable_extensions(self, _):
331331
self.addCleanup(harness.cleanup)
332332
harness.begin()
333333
harness.charm.enable_disable_extensions()
334-
self.assertEqual(postgresql_mock.enable_disable_extension.call_count, 6)
334+
self.assertEqual(postgresql_mock.enable_disable_extensions.call_count, 1)
335335

336336
@patch("charm.PostgresqlOperatorCharm.enable_disable_extensions")
337337
@patch("charm.snap.SnapCache")

tests/unit/test_db.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,12 @@ def test_get_extensions(self):
185185
@patch("subprocess.check_output", return_value=b"C")
186186
@patch("relations.db.DbProvides._update_unit_status")
187187
@patch("relations.db.DbProvides.update_endpoints")
188-
@patch("charm.PostgresqlOperatorCharm.enable_disable_extensions")
189188
@patch("relations.db.new_password", return_value="test-password")
190189
@patch("relations.db.DbProvides._get_extensions")
191190
def test_set_up_relation(
192191
self,
193192
_get_extensions,
194193
_new_password,
195-
_enable_disable_extensions,
196194
_update_endpoints,
197195
_update_unit_status,
198196
_,
@@ -222,7 +220,6 @@ def test_set_up_relation(
222220
self.assertFalse(self.harness.charm.legacy_db_relation.set_up_relation(relation))
223221
postgresql_mock.create_user.assert_not_called()
224222
postgresql_mock.create_database.assert_not_called()
225-
_enable_disable_extensions.assert_not_called()
226223
postgresql_mock.get_postgresql_version.assert_not_called()
227224
_update_endpoints.assert_not_called()
228225
_update_unit_status.assert_not_called()
@@ -239,7 +236,6 @@ def test_set_up_relation(
239236
user = f"relation-{self.rel_id}"
240237
postgresql_mock.create_user.assert_called_once_with(user, "test-password", False)
241238
postgresql_mock.create_database.assert_called_once_with(DATABASE, user, plugins=[])
242-
_enable_disable_extensions.assert_called_once()
243239
_update_endpoints.assert_called_once()
244240
_update_unit_status.assert_called_once()
245241
self.assertNotIsInstance(self.harness.model.unit.status, BlockedStatus)
@@ -248,7 +244,6 @@ def test_set_up_relation(
248244
# provided in both application and unit databags.
249245
postgresql_mock.create_user.reset_mock()
250246
postgresql_mock.create_database.reset_mock()
251-
_enable_disable_extensions.reset_mock()
252247
postgresql_mock.get_postgresql_version.reset_mock()
253248
_update_endpoints.reset_mock()
254249
_update_unit_status.reset_mock()
@@ -266,15 +261,13 @@ def test_set_up_relation(
266261
self.assertTrue(self.harness.charm.legacy_db_relation.set_up_relation(relation))
267262
postgresql_mock.create_user.assert_called_once_with(user, "test-password", False)
268263
postgresql_mock.create_database.assert_called_once_with(DATABASE, user, plugins=[])
269-
_enable_disable_extensions.assert_called_once()
270264
_update_endpoints.assert_called_once()
271265
_update_unit_status.assert_called_once()
272266
self.assertNotIsInstance(self.harness.model.unit.status, BlockedStatus)
273267

274268
# Assert that the correct calls were made when the database name is not provided.
275269
postgresql_mock.create_user.reset_mock()
276270
postgresql_mock.create_database.reset_mock()
277-
_enable_disable_extensions.reset_mock()
278271
postgresql_mock.get_postgresql_version.reset_mock()
279272
_update_endpoints.reset_mock()
280273
_update_unit_status.reset_mock()
@@ -289,28 +282,24 @@ def test_set_up_relation(
289282
postgresql_mock.create_database.assert_called_once_with(
290283
"application", user, plugins=[]
291284
)
292-
_enable_disable_extensions.assert_called_once()
293285
_update_endpoints.assert_called_once()
294286
_update_unit_status.assert_called_once()
295287
self.assertNotIsInstance(self.harness.model.unit.status, BlockedStatus)
296288

297289
# BlockedStatus due to a PostgreSQLCreateUserError.
298290
postgresql_mock.create_database.reset_mock()
299-
_enable_disable_extensions.reset_mock()
300291
postgresql_mock.get_postgresql_version.reset_mock()
301292
_update_endpoints.reset_mock()
302293
_update_unit_status.reset_mock()
303294
self.assertFalse(self.harness.charm.legacy_db_relation.set_up_relation(relation))
304295
postgresql_mock.create_database.assert_not_called()
305-
_enable_disable_extensions.assert_not_called()
306296
_update_endpoints.assert_not_called()
307297
_update_unit_status.assert_not_called()
308298
self.assertIsInstance(self.harness.model.unit.status, BlockedStatus)
309299

310300
# BlockedStatus due to a PostgreSQLCreateDatabaseError.
311301
self.harness.charm.unit.status = ActiveStatus()
312302
self.assertFalse(self.harness.charm.legacy_db_relation.set_up_relation(relation))
313-
_enable_disable_extensions.assert_not_called()
314303
_update_endpoints.assert_not_called()
315304
_update_unit_status.assert_not_called()
316305
self.assertIsInstance(self.harness.model.unit.status, BlockedStatus)

0 commit comments

Comments
 (0)