Merge "[keystone-k8s] configure charm when rotate id-svc fails" into main
This commit is contained in:
commit
da9f54d56f
@ -46,6 +46,7 @@ import charms.keystone_k8s.v0.identity_credentials as sunbeam_cc_svc
|
||||
import charms.keystone_k8s.v0.identity_resource as sunbeam_ops_svc
|
||||
import charms.keystone_k8s.v1.identity_service as sunbeam_id_svc
|
||||
import jinja2
|
||||
import keystoneauth1.exceptions
|
||||
import ops.charm
|
||||
import ops.pebble
|
||||
import ops_sunbeam.charm as sunbeam_charm
|
||||
@ -781,10 +782,10 @@ export OS_AUTH_VERSION=3
|
||||
and len(CREDENTIALS_SECRET_PREFIX) : # noqa: E203
|
||||
]
|
||||
username = f"{username}-{suffix}"
|
||||
password = pwgen.pwgen(12)
|
||||
password = str(pwgen.pwgen(12))
|
||||
|
||||
logger.info(f"Creating service account with username {username}")
|
||||
self.keystone_manager.create_service_account(username, password)
|
||||
self._create_service_account_with_retry(event, username, password)
|
||||
olduser = event.secret.get_content(refresh=True).get("username")
|
||||
event.secret.set_content(
|
||||
{"username": username, "password": password}
|
||||
@ -799,6 +800,29 @@ export OS_AUTH_VERSION=3
|
||||
{"old_service_users": json.dumps(service_users_to_delete)}
|
||||
)
|
||||
|
||||
def _create_service_account_with_retry(
|
||||
self, event: ops.EventBase, username: str, password: str
|
||||
) -> dict:
|
||||
"""Create a service account, tries to configure the charm if connection fails."""
|
||||
try:
|
||||
return self.keystone_manager.create_service_account(
|
||||
username, password
|
||||
)
|
||||
except keystoneauth1.exceptions.ConnectFailure:
|
||||
logger.debug("Failed to connect to keystone", exc_info=True)
|
||||
self.configure_charm(event)
|
||||
if self.status.status.name != "active":
|
||||
logger.debug("Configure charm did not configure to completion")
|
||||
# note(gboutry): This is not caught by a sunbeam guard,
|
||||
# this will fail the hook.
|
||||
raise sunbeam_guard.BlockedExceptionError(
|
||||
"Failed to create service account"
|
||||
)
|
||||
# note(gboutry): If successfully configured, retry the service account creation
|
||||
# let bubble up this exception, if it fails again,
|
||||
# this hook should be retried again in the future.
|
||||
return self.keystone_manager.create_service_account(username, password)
|
||||
|
||||
def _on_secret_remove(self, event: ops.charm.SecretRemoveEvent):
|
||||
logger.info(f"secret-remove triggered for label {event.secret.label}")
|
||||
if (
|
||||
|
@ -26,6 +26,8 @@ from unittest.mock import (
|
||||
)
|
||||
|
||||
import charm
|
||||
import keystoneauth1.exceptions
|
||||
import ops_sunbeam.guard as sunbeam_guard
|
||||
import ops_sunbeam.test_utils as test_utils
|
||||
|
||||
|
||||
@ -56,7 +58,7 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
||||
"pwgen",
|
||||
]
|
||||
|
||||
def add_id_relation(self) -> str:
|
||||
def add_id_relation(self) -> int:
|
||||
"""Add amqp relation."""
|
||||
rel_id = self.harness.add_relation("identity-service", "cinder")
|
||||
self.harness.add_relation_unit(rel_id, "cinder/0")
|
||||
@ -415,6 +417,137 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
||||
"""Test secret-rotate event for label credential_keys on non leader unit."""
|
||||
self._test_non_leader_on_secret_rotate(label="credential-keys")
|
||||
|
||||
def test_leader_on_secret_rotate_identity_service_secret(self):
|
||||
"""Test secret-rotate event for label identity_service_secret on leader unit."""
|
||||
create_service_account = MagicMock(side_effect=[{"name": "cinder"}])
|
||||
configure_mock = MagicMock(
|
||||
side_effect=self.harness.charm.configure_charm
|
||||
)
|
||||
|
||||
self._test_secret_rotate_identity_credentials(
|
||||
create_service_mock=create_service_account,
|
||||
configure_mock=configure_mock,
|
||||
)
|
||||
self.assertEqual(create_service_account.call_count, 1)
|
||||
self.assertEqual(configure_mock.call_count, 0)
|
||||
|
||||
def test_leader_on_secret_rotate_identity_service_secret_when_failing_to_connect_once(
|
||||
self,
|
||||
):
|
||||
"""When the secret rotate hook fails to connect once, it should retry.
|
||||
|
||||
The hook will try to configure the charm, and then retry to create the service account.
|
||||
"""
|
||||
create_service_account = MagicMock(
|
||||
side_effect=[
|
||||
keystoneauth1.exceptions.ConnectFailure(
|
||||
"Failed to connect..."
|
||||
),
|
||||
{"name": "cinder"},
|
||||
]
|
||||
)
|
||||
configure_mock = MagicMock(
|
||||
side_effect=self.harness.charm.configure_charm
|
||||
)
|
||||
|
||||
self._test_secret_rotate_identity_credentials(
|
||||
create_service_mock=create_service_account,
|
||||
configure_mock=configure_mock,
|
||||
)
|
||||
self.assertEqual(create_service_account.call_count, 2)
|
||||
self.assertEqual(configure_mock.call_count, 1)
|
||||
|
||||
def test_leader_on_secret_rotate_identity_service_secret_when_failing_to_connect_twice(
|
||||
self,
|
||||
):
|
||||
"""This will fail to connect twice, and then raise an error."""
|
||||
create_service_account = MagicMock(
|
||||
side_effect=[
|
||||
keystoneauth1.exceptions.ConnectFailure(
|
||||
"Failed to connect..."
|
||||
),
|
||||
keystoneauth1.exceptions.ConnectFailure(
|
||||
"Failed to connect..."
|
||||
),
|
||||
]
|
||||
)
|
||||
configure_mock = MagicMock(
|
||||
side_effect=self.harness.charm.configure_charm
|
||||
)
|
||||
|
||||
with self.assertRaises(keystoneauth1.exceptions.ConnectFailure):
|
||||
self._test_secret_rotate_identity_credentials(
|
||||
create_service_mock=create_service_account,
|
||||
configure_mock=configure_mock,
|
||||
)
|
||||
self.assertEqual(create_service_account.call_count, 2)
|
||||
self.assertEqual(configure_mock.call_count, 1)
|
||||
|
||||
def test_leader_on_secret_rotate_identity_service_secret_when_unexpected_error(
|
||||
self,
|
||||
):
|
||||
"""This is an unhandled exception, it should have been bubbled up."""
|
||||
create_service_account = MagicMock(
|
||||
side_effect=Exception("I am unexpected..."),
|
||||
)
|
||||
configure_mock = MagicMock(
|
||||
side_effect=self.harness.charm.configure_charm
|
||||
)
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
self._test_secret_rotate_identity_credentials(
|
||||
create_service_mock=create_service_account,
|
||||
configure_mock=configure_mock,
|
||||
)
|
||||
self.assertEqual(create_service_account.call_count, 1)
|
||||
self.assertEqual(configure_mock.call_count, 0)
|
||||
|
||||
def test_leader_on_secret_rotate_identity_service_secret_when_configured_not_active(
|
||||
self,
|
||||
):
|
||||
"""Keystone is not ready, it should raise a blocked exception."""
|
||||
create_service_account = MagicMock(
|
||||
side_effect=keystoneauth1.exceptions.ConnectFailure(
|
||||
"Failed to connect..."
|
||||
),
|
||||
)
|
||||
configure_mock = MagicMock(
|
||||
side_effect=self.harness.charm.configure_charm
|
||||
)
|
||||
with self.assertRaises(sunbeam_guard.BlockedExceptionError):
|
||||
self._test_secret_rotate_identity_credentials(
|
||||
create_service_mock=create_service_account,
|
||||
configure_mock=configure_mock,
|
||||
remove_ingress=True,
|
||||
)
|
||||
self.assertEqual(create_service_account.call_count, 1)
|
||||
self.assertEqual(configure_mock.call_count, 1)
|
||||
|
||||
def _test_secret_rotate_identity_credentials(
|
||||
self,
|
||||
create_service_mock: MagicMock,
|
||||
configure_mock: MagicMock,
|
||||
remove_ingress=False,
|
||||
):
|
||||
test_utils.add_complete_ingress_relation(self.harness)
|
||||
test_utils.add_complete_db_relation(self.harness)
|
||||
test_utils.add_complete_peer_relation(self.harness)
|
||||
self.harness.set_leader()
|
||||
self.harness.container_pebble_ready("keystone")
|
||||
rel_id = self.add_id_relation()
|
||||
rel_data = self.harness.get_relation_data(
|
||||
rel_id, self.harness.model.app.name
|
||||
)
|
||||
label = charm.CREDENTIALS_SECRET_PREFIX + "svc_" + "cinder"
|
||||
secret_id = rel_data["service-credentials"]
|
||||
if remove_ingress:
|
||||
rel = self.harness.charm.model.get_relation("ingress-public")
|
||||
rel_id = rel.id
|
||||
self.harness.remove_relation(rel_id)
|
||||
self.km_mock.create_service_account = create_service_mock
|
||||
self.harness.charm.configure_charm = configure_mock
|
||||
self.harness.trigger_secret_rotation(secret_id, label=label)
|
||||
|
||||
def test_on_secret_changed_with_fernet_keys_and_fernet_secret_same(self):
|
||||
"""Test secret change event when fernet keys and secret have same content."""
|
||||
test_utils.add_complete_ingress_relation(self.harness)
|
||||
|
Loading…
x
Reference in New Issue
Block a user