Merge "Migrate database interface" into main
This commit is contained in:
commit
bd70b928e3
@ -52,8 +52,8 @@ class TestAodhOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -62,7 +62,7 @@ class TestAodhOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ class TestBarbicanOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -64,7 +64,7 @@ class TestBarbicanOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ class TestCinderCephOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -104,7 +104,7 @@ class TestCinderCephOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ class TestDesignateOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -69,7 +69,7 @@ class TestDesignateOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ class TestGlanceOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -69,7 +69,7 @@ class TestGlanceOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ class TestGnocchiCephOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -69,7 +69,7 @@ class TestGnocchiCephOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ class TestHeatOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -73,7 +73,7 @@ class TestHeatOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ class TestHorizonOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -65,7 +65,7 @@ class TestHorizonOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -148,8 +148,8 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -158,7 +158,7 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -288,16 +288,11 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
|||||||
updated_fernet_keys = {
|
updated_fernet_keys = {
|
||||||
"0": "Qf4vHdf6XC2dGKpEwtGapq7oDOqUWepcH2tKgQ0qOKc=",
|
"0": "Qf4vHdf6XC2dGKpEwtGapq7oDOqUWepcH2tKgQ0qOKc=",
|
||||||
"2": "UK3qzLGvu-piYwau0BFyed8O3WP8lFKH_v1sXYulzhs=",
|
"2": "UK3qzLGvu-piYwau0BFyed8O3WP8lFKH_v1sXYulzhs=",
|
||||||
"3": "YVYUJbQNASbVzzntqj2sG9rbDOV_QQfueDCz0PJEKKw=",
|
"3": "yvyujbqnasbvzzntqj2sg9rbdov_qqfuedcz0pjekkw=",
|
||||||
|
}
|
||||||
|
secret_fernet_keys = {
|
||||||
|
f"fernet-{k}": v for k, v in updated_fernet_keys.items()
|
||||||
}
|
}
|
||||||
secret_mock = mock.MagicMock()
|
|
||||||
secret_mock.id = "test-secret-id"
|
|
||||||
secret_mock.get_content.return_value = updated_fernet_keys
|
|
||||||
|
|
||||||
self.harness.model.app.add_secret = MagicMock()
|
|
||||||
self.harness.model.app.add_secret.return_value = secret_mock
|
|
||||||
self.harness.model.get_secret = MagicMock()
|
|
||||||
self.harness.model.get_secret.return_value = secret_mock
|
|
||||||
|
|
||||||
test_utils.add_complete_ingress_relation(self.harness)
|
test_utils.add_complete_ingress_relation(self.harness)
|
||||||
self.harness.set_leader()
|
self.harness.set_leader()
|
||||||
@ -308,9 +303,22 @@ class TestKeystoneOperatorCharm(test_utils.CharmTestCase):
|
|||||||
self.harness, test_utils.add_base_db_relation(self.harness)
|
self.harness, test_utils.add_base_db_relation(self.harness)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
secret_id = self.harness.get_relation_data(rel_id, "keystone-k8s")[
|
||||||
|
"fernet-secret-id"
|
||||||
|
]
|
||||||
|
s = self.harness.model.get_secret(id=secret_id)
|
||||||
|
s.set_content(secret_fernet_keys)
|
||||||
|
s.get_content(refresh=True)
|
||||||
|
secret_id = self.harness.get_relation_data(rel_id, "keystone-k8s")[
|
||||||
|
"credential-keys-secret-id"
|
||||||
|
]
|
||||||
|
s = self.harness.model.get_secret(id=secret_id)
|
||||||
|
s.set_content(secret_fernet_keys)
|
||||||
|
s.get_content(refresh=True)
|
||||||
|
|
||||||
event = MagicMock()
|
event = MagicMock()
|
||||||
self.harness.charm._on_peer_data_changed(event)
|
self.harness.charm._on_peer_data_changed(event)
|
||||||
self.assertTrue(self.harness.model.get_secret.called)
|
|
||||||
self.assertTrue(self.km_mock.read_keys.called)
|
self.assertTrue(self.km_mock.read_keys.called)
|
||||||
self.assertEqual(self.km_mock.write_keys.call_count, 2)
|
self.assertEqual(self.km_mock.write_keys.call_count, 2)
|
||||||
self.km_mock.write_keys.assert_has_calls(
|
self.km_mock.write_keys.assert_has_calls(
|
||||||
|
@ -62,8 +62,8 @@ class TestMagnumOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -72,7 +72,7 @@ class TestMagnumOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ class TestNeutronOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -68,7 +68,7 @@ class TestNeutronOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ class TestNovaOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -70,7 +70,7 @@ class TestNovaOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"cell_database_read_only_endpoints_changed",
|
"cell_database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ class TestOctaviaOVNOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -64,7 +64,7 @@ class TestOctaviaOVNOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ class TestPlacementOperatorCharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -71,7 +71,7 @@ class TestPlacementOperatorCharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
pushd libs/external
|
pushd libs/external
|
||||||
|
|
||||||
echo "INFO: Fetching libs from charmhub."
|
echo "INFO: Fetching libs from charmhub."
|
||||||
charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
|
charmcraft fetch-lib charms.data_platform_libs.v0.data_interfaces
|
||||||
charmcraft fetch-lib charms.grafana_k8s.v0.grafana_auth
|
charmcraft fetch-lib charms.grafana_k8s.v0.grafana_auth
|
||||||
charmcraft fetch-lib charms.observability_libs.v1.kubernetes_service_patch
|
charmcraft fetch-lib charms.observability_libs.v1.kubernetes_service_patch
|
||||||
charmcraft fetch-lib charms.operator_libs_linux.v2.snap
|
charmcraft fetch-lib charms.operator_libs_linux.v2.snap
|
||||||
|
2684
libs/external/lib/charms/data_platform_libs/v0/data_interfaces.py
vendored
Normal file
2684
libs/external/lib/charms/data_platform_libs/v0/data_interfaces.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,496 +0,0 @@
|
|||||||
# Copyright 2022 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Relation 'requires' side abstraction for database relation.
|
|
||||||
|
|
||||||
This library is a uniform interface to a selection of common database
|
|
||||||
metadata, with added custom events that add convenience to database management,
|
|
||||||
and methods to consume the application related data.
|
|
||||||
|
|
||||||
Following an example of using the DatabaseCreatedEvent, in the context of the
|
|
||||||
application charm code:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
from charms.data_platform_libs.v0.database_requires import DatabaseRequires
|
|
||||||
|
|
||||||
class ApplicationCharm(CharmBase):
|
|
||||||
# Application charm that connects to database charms.
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
|
|
||||||
# Charm events defined in the database requires charm library.
|
|
||||||
self.database = DatabaseRequires(self, relation_name="database", database_name="database")
|
|
||||||
self.framework.observe(self.database.on.database_created, self._on_database_created)
|
|
||||||
|
|
||||||
def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start application with rendered configuration
|
|
||||||
self._start_application(config_file)
|
|
||||||
|
|
||||||
# Set active status
|
|
||||||
self.unit.status = ActiveStatus("received database credentials")
|
|
||||||
```
|
|
||||||
|
|
||||||
As shown above, the library provides some custom events to handle specific situations,
|
|
||||||
which are listed below:
|
|
||||||
|
|
||||||
— database_created: event emitted when the requested database is created.
|
|
||||||
— endpoints_changed: event emitted when the read/write endpoints of the database have changed.
|
|
||||||
— read_only_endpoints_changed: event emitted when the read-only endpoints of the database
|
|
||||||
have changed. Event is not triggered if read/write endpoints changed too.
|
|
||||||
|
|
||||||
If it is needed to connect multiple database clusters to the same relation endpoint
|
|
||||||
the application charm can implement the same code as if it would connect to only
|
|
||||||
one database cluster (like the above code example).
|
|
||||||
|
|
||||||
To differentiate multiple clusters connected to the same relation endpoint
|
|
||||||
the application charm can use the name of the remote application:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Get the remote app name of the cluster that triggered this event
|
|
||||||
cluster = event.relation.app.name
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to provide an alias for each different database cluster/relation.
|
|
||||||
|
|
||||||
So, it is possible to differentiate the clusters in two ways.
|
|
||||||
The first is to use the remote application name, i.e., `event.relation.app.name`, as above.
|
|
||||||
|
|
||||||
The second way is to use different event handlers to handle each cluster events.
|
|
||||||
The implementation would be something like the following code:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
from charms.data_platform_libs.v0.database_requires import DatabaseRequires
|
|
||||||
|
|
||||||
class ApplicationCharm(CharmBase):
|
|
||||||
# Application charm that connects to database charms.
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
|
|
||||||
# Define the cluster aliases and one handler for each cluster database created event.
|
|
||||||
self.database = DatabaseRequires(
|
|
||||||
self,
|
|
||||||
relation_name="database",
|
|
||||||
database_name="database",
|
|
||||||
relations_aliases = ["cluster1", "cluster2"],
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.database.on.cluster1_database_created, self._on_cluster1_database_created
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.database.on.cluster2_database_created, self._on_cluster2_database_created
|
|
||||||
)
|
|
||||||
|
|
||||||
def _on_cluster1_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database on the cluster named cluster1
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
...
|
|
||||||
|
|
||||||
def _on_cluster2_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database on the cluster named cluster2
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
...
|
|
||||||
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from collections import namedtuple
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from ops.charm import (
|
|
||||||
CharmEvents,
|
|
||||||
RelationChangedEvent,
|
|
||||||
RelationEvent,
|
|
||||||
RelationJoinedEvent,
|
|
||||||
)
|
|
||||||
from ops.framework import EventSource, Object
|
|
||||||
from ops.model import Relation
|
|
||||||
|
|
||||||
# The unique Charmhub library identifier, never change it
|
|
||||||
LIBID = "0241e088ffa9440fb4e3126349b2fb62"
|
|
||||||
|
|
||||||
# Increment this major API version when introducing breaking changes
|
|
||||||
LIBAPI = 0
|
|
||||||
|
|
||||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
|
||||||
# to 0 if you are raising the major API version.
|
|
||||||
LIBPATCH = 4
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEvent(RelationEvent):
|
|
||||||
"""Base class for database events."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def endpoints(self) -> Optional[str]:
|
|
||||||
"""Returns a comma separated list of read/write endpoints."""
|
|
||||||
return self.relation.data[self.relation.app].get("endpoints")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def password(self) -> Optional[str]:
|
|
||||||
"""Returns the password for the created user."""
|
|
||||||
return self.relation.data[self.relation.app].get("password")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def read_only_endpoints(self) -> Optional[str]:
|
|
||||||
"""Returns a comma separated list of read only endpoints."""
|
|
||||||
return self.relation.data[self.relation.app].get("read-only-endpoints")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def replset(self) -> Optional[str]:
|
|
||||||
"""Returns the replicaset name.
|
|
||||||
|
|
||||||
MongoDB only.
|
|
||||||
"""
|
|
||||||
return self.relation.data[self.relation.app].get("replset")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tls(self) -> Optional[str]:
|
|
||||||
"""Returns whether TLS is configured."""
|
|
||||||
return self.relation.data[self.relation.app].get("tls")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tls_ca(self) -> Optional[str]:
|
|
||||||
"""Returns TLS CA."""
|
|
||||||
return self.relation.data[self.relation.app].get("tls-ca")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uris(self) -> Optional[str]:
|
|
||||||
"""Returns the connection URIs.
|
|
||||||
|
|
||||||
MongoDB, Redis, OpenSearch and Kafka only.
|
|
||||||
"""
|
|
||||||
return self.relation.data[self.relation.app].get("uris")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username(self) -> Optional[str]:
|
|
||||||
"""Returns the created username."""
|
|
||||||
return self.relation.data[self.relation.app].get("username")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def version(self) -> Optional[str]:
|
|
||||||
"""Returns the version of the database.
|
|
||||||
|
|
||||||
Version as informed by the database daemon.
|
|
||||||
"""
|
|
||||||
return self.relation.data[self.relation.app].get("version")
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseCreatedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when a new database is created for use on this relation."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEndpointsChangedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when the read/write endpoints are changed."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseReadOnlyEndpointsChangedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when the read only endpoints are changed."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEvents(CharmEvents):
|
|
||||||
"""Database events.
|
|
||||||
|
|
||||||
This class defines the events that the database can emit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
database_created = EventSource(DatabaseCreatedEvent)
|
|
||||||
endpoints_changed = EventSource(DatabaseEndpointsChangedEvent)
|
|
||||||
read_only_endpoints_changed = EventSource(DatabaseReadOnlyEndpointsChangedEvent)
|
|
||||||
|
|
||||||
|
|
||||||
Diff = namedtuple("Diff", "added changed deleted")
|
|
||||||
Diff.__doc__ = """
|
|
||||||
A tuple for storing the diff between two data mappings.
|
|
||||||
|
|
||||||
— added — keys that were added.
|
|
||||||
— changed — keys that still exist but have new values.
|
|
||||||
— deleted — keys that were deleted.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseRequires(Object):
|
|
||||||
"""Requires-side of the database relation."""
|
|
||||||
|
|
||||||
on = DatabaseEvents()
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
charm,
|
|
||||||
relation_name: str,
|
|
||||||
database_name: str,
|
|
||||||
extra_user_roles: str = None,
|
|
||||||
relations_aliases: List[str] = None,
|
|
||||||
):
|
|
||||||
"""Manager of database client relations."""
|
|
||||||
super().__init__(charm, relation_name)
|
|
||||||
self.charm = charm
|
|
||||||
self.database = database_name
|
|
||||||
self.extra_user_roles = extra_user_roles
|
|
||||||
self.local_app = self.charm.model.app
|
|
||||||
self.local_unit = self.charm.unit
|
|
||||||
self.relation_name = relation_name
|
|
||||||
self.relations_aliases = relations_aliases
|
|
||||||
self.framework.observe(
|
|
||||||
self.charm.on[relation_name].relation_joined, self._on_relation_joined_event
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.charm.on[relation_name].relation_changed, self._on_relation_changed_event
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define custom event names for each alias.
|
|
||||||
if relations_aliases:
|
|
||||||
# Ensure the number of aliases does not exceed the maximum
|
|
||||||
# of connections allowed in the specific relation.
|
|
||||||
relation_connection_limit = self.charm.meta.requires[relation_name].limit
|
|
||||||
if len(relations_aliases) != relation_connection_limit:
|
|
||||||
raise ValueError(
|
|
||||||
f"The number of aliases must match the maximum number of connections allowed in the relation. "
|
|
||||||
f"Expected {relation_connection_limit}, got {len(relations_aliases)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for relation_alias in relations_aliases:
|
|
||||||
self.on.define_event(f"{relation_alias}_database_created", DatabaseCreatedEvent)
|
|
||||||
self.on.define_event(
|
|
||||||
f"{relation_alias}_endpoints_changed", DatabaseEndpointsChangedEvent
|
|
||||||
)
|
|
||||||
self.on.define_event(
|
|
||||||
f"{relation_alias}_read_only_endpoints_changed",
|
|
||||||
DatabaseReadOnlyEndpointsChangedEvent,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _assign_relation_alias(self, relation_id: int) -> None:
|
|
||||||
"""Assigns an alias to a relation.
|
|
||||||
|
|
||||||
This function writes in the unit data bag.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
"""
|
|
||||||
# If no aliases were provided, return immediately.
|
|
||||||
if not self.relations_aliases:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Return if an alias was already assigned to this relation
|
|
||||||
# (like when there are more than one unit joining the relation).
|
|
||||||
if (
|
|
||||||
self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
.data[self.local_unit]
|
|
||||||
.get("alias")
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Retrieve the available aliases (the ones that weren't assigned to any relation).
|
|
||||||
available_aliases = self.relations_aliases[:]
|
|
||||||
for relation in self.charm.model.relations[self.relation_name]:
|
|
||||||
alias = relation.data[self.local_unit].get("alias")
|
|
||||||
if alias:
|
|
||||||
logger.debug("Alias %s was already assigned to relation %d", alias, relation.id)
|
|
||||||
available_aliases.remove(alias)
|
|
||||||
|
|
||||||
# Set the alias in the unit relation databag of the specific relation.
|
|
||||||
relation = self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
relation.data[self.local_unit].update({"alias": available_aliases[0]})
|
|
||||||
|
|
||||||
def _diff(self, event: RelationChangedEvent) -> Diff:
|
|
||||||
"""Retrieves the diff of the data in the relation changed databag.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event: relation changed event.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
a Diff instance containing the added, deleted and changed
|
|
||||||
keys from the event relation databag.
|
|
||||||
"""
|
|
||||||
# Retrieve the old data from the data key in the local unit relation databag.
|
|
||||||
old_data = json.loads(event.relation.data[self.local_unit].get("data", "{}"))
|
|
||||||
# Retrieve the new data from the event relation databag.
|
|
||||||
new_data = {
|
|
||||||
key: value for key, value in event.relation.data[event.app].items() if key != "data"
|
|
||||||
}
|
|
||||||
|
|
||||||
# These are the keys that were added to the databag and triggered this event.
|
|
||||||
added = new_data.keys() - old_data.keys()
|
|
||||||
# These are the keys that were removed from the databag and triggered this event.
|
|
||||||
deleted = old_data.keys() - new_data.keys()
|
|
||||||
# These are the keys that already existed in the databag,
|
|
||||||
# but had their values changed.
|
|
||||||
changed = {
|
|
||||||
key for key in old_data.keys() & new_data.keys() if old_data[key] != new_data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: evaluate the possibility of losing the diff if some error
|
|
||||||
# happens in the charm before the diff is completely checked (DPE-412).
|
|
||||||
# Convert the new_data to a serializable format and save it for a next diff check.
|
|
||||||
event.relation.data[self.local_unit].update({"data": json.dumps(new_data)})
|
|
||||||
|
|
||||||
# Return the diff with all possible changes.
|
|
||||||
return Diff(added, changed, deleted)
|
|
||||||
|
|
||||||
def _emit_aliased_event(self, event: RelationChangedEvent, event_name: str) -> None:
|
|
||||||
"""Emit an aliased event to a particular relation if it has an alias.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event: the relation changed event that was received.
|
|
||||||
event_name: the name of the event to emit.
|
|
||||||
"""
|
|
||||||
alias = self._get_relation_alias(event.relation.id)
|
|
||||||
if alias:
|
|
||||||
getattr(self.on, f"{alias}_{event_name}").emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_relation_alias(self, relation_id: int) -> Optional[str]:
|
|
||||||
"""Returns the relation alias.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
the relation alias or None if the relation was not found.
|
|
||||||
"""
|
|
||||||
for relation in self.charm.model.relations[self.relation_name]:
|
|
||||||
if relation.id == relation_id:
|
|
||||||
return relation.data[self.local_unit].get("alias")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def fetch_relation_data(self) -> dict:
|
|
||||||
"""Retrieves data from relation.
|
|
||||||
|
|
||||||
This function can be used to retrieve data from a relation
|
|
||||||
in the charm code when outside an event callback.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
a dict of the values stored in the relation data bag
|
|
||||||
for all relation instances (indexed by the relation ID).
|
|
||||||
"""
|
|
||||||
data = {}
|
|
||||||
for relation in self.relations:
|
|
||||||
data[relation.id] = {
|
|
||||||
key: value for key, value in relation.data[relation.app].items() if key != "data"
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _update_relation_data(self, relation_id: int, data: dict) -> None:
|
|
||||||
"""Updates a set of key-value pairs in the relation.
|
|
||||||
|
|
||||||
This function writes in the application data bag, therefore,
|
|
||||||
only the leader unit can call it.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
data: dict containing the key-value pairs
|
|
||||||
that should be updated in the relation.
|
|
||||||
"""
|
|
||||||
if self.local_unit.is_leader():
|
|
||||||
relation = self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
relation.data[self.local_app].update(data)
|
|
||||||
|
|
||||||
def _on_relation_joined_event(self, event: RelationJoinedEvent) -> None:
|
|
||||||
"""Event emitted when the application joins the database relation."""
|
|
||||||
# If relations aliases were provided, assign one to the relation.
|
|
||||||
self._assign_relation_alias(event.relation.id)
|
|
||||||
|
|
||||||
# Sets both database and extra user roles in the relation
|
|
||||||
# if the roles are provided. Otherwise, sets only the database.
|
|
||||||
if self.extra_user_roles:
|
|
||||||
self._update_relation_data(
|
|
||||||
event.relation.id,
|
|
||||||
{
|
|
||||||
"database": self.database,
|
|
||||||
"extra-user-roles": self.extra_user_roles,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._update_relation_data(event.relation.id, {"database": self.database})
|
|
||||||
|
|
||||||
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
|
|
||||||
"""Event emitted when the database relation has changed."""
|
|
||||||
# Check which data has changed to emit customs events.
|
|
||||||
diff = self._diff(event)
|
|
||||||
|
|
||||||
# Check if the database is created
|
|
||||||
# (the database charm shared the credentials).
|
|
||||||
if "username" in diff.added and "password" in diff.added:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("database created at %s", datetime.now())
|
|
||||||
self.on.database_created.emit(event.relation, app=event.app, unit=event.unit)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "database_created")
|
|
||||||
|
|
||||||
# To avoid unnecessary application restarts do not trigger
|
|
||||||
# “endpoints_changed“ event if “database_created“ is triggered.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Emit an endpoints changed event if the database
|
|
||||||
# added or changed this info in the relation databag.
|
|
||||||
if "endpoints" in diff.added or "endpoints" in diff.changed:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("endpoints changed on %s", datetime.now())
|
|
||||||
self.on.endpoints_changed.emit(event.relation, app=event.app, unit=event.unit)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "endpoints_changed")
|
|
||||||
|
|
||||||
# To avoid unnecessary application restarts do not trigger
|
|
||||||
# “read_only_endpoints_changed“ event if “endpoints_changed“ is triggered.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Emit a read only endpoints changed event if the database
|
|
||||||
# added or changed this info in the relation databag.
|
|
||||||
if "read-only-endpoints" in diff.added or "read-only-endpoints" in diff.changed:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("read-only-endpoints changed on %s", datetime.now())
|
|
||||||
self.on.read_only_endpoints_changed.emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "read_only_endpoints_changed")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def relations(self) -> List[Relation]:
|
|
||||||
"""The list of Relation instances associated with this relation_name."""
|
|
||||||
return list(self.charm.model.relations[self.relation_name])
|
|
@ -42,7 +42,7 @@ Fetch interface libs corresponding to the requires interfaces:
|
|||||||
charmcraft login --export ~/secrets.auth
|
charmcraft login --export ~/secrets.auth
|
||||||
export CHARMCRAFT_AUTH=$(cat ~/secrets.auth)
|
export CHARMCRAFT_AUTH=$(cat ~/secrets.auth)
|
||||||
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
||||||
charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
|
charmcraft fetch-lib charms.data_platform_libs.v0.data_interfaces
|
||||||
charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
||||||
charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq
|
charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq
|
||||||
charmcraft fetch-lib charms.traefik_k8s.v1.ingress
|
charmcraft fetch-lib charms.traefik_k8s.v1.ingress
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
echo "WARNING: Charm interface libs are excluded from ASO python package."
|
echo "WARNING: Charm interface libs are excluded from ASO python package."
|
||||||
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
||||||
charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
|
charmcraft fetch-lib charms.data_platform_libs.v0.data_interfaces
|
||||||
charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
||||||
charmcraft fetch-lib charms.keystone_k8s.v0.identity_credentials
|
charmcraft fetch-lib charms.keystone_k8s.v0.identity_credentials
|
||||||
charmcraft fetch-lib charms.keystone_k8s.v0.identity_resource
|
charmcraft fetch-lib charms.keystone_k8s.v0.identity_resource
|
||||||
|
@ -275,7 +275,7 @@ class DBHandler(RelationHandler):
|
|||||||
# Import here to avoid import errors if ops_sunbeam is being used
|
# Import here to avoid import errors if ops_sunbeam is being used
|
||||||
# with a charm that doesn't want a DBHandler
|
# with a charm that doesn't want a DBHandler
|
||||||
# and doesn't install this database_requires library.
|
# and doesn't install this database_requires library.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseRequires,
|
DatabaseRequires,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -342,11 +342,7 @@ class DBHandler(RelationHandler):
|
|||||||
def ready(self) -> bool:
|
def ready(self) -> bool:
|
||||||
"""Whether the handler is ready for use."""
|
"""Whether the handler is ready for use."""
|
||||||
data = self.get_relation_data()
|
data = self.get_relation_data()
|
||||||
return bool(
|
return bool(data.get("endpoints") and data.get("secret-user"))
|
||||||
data.get("endpoints")
|
|
||||||
and data.get("username")
|
|
||||||
and data.get("password")
|
|
||||||
)
|
|
||||||
|
|
||||||
def context(self) -> dict:
|
def context(self) -> dict:
|
||||||
"""Context containing database connection data."""
|
"""Context containing database connection data."""
|
||||||
@ -356,8 +352,10 @@ class DBHandler(RelationHandler):
|
|||||||
data = self.get_relation_data()
|
data = self.get_relation_data()
|
||||||
database_name = self.database_name
|
database_name = self.database_name
|
||||||
database_host = data["endpoints"]
|
database_host = data["endpoints"]
|
||||||
database_user = data["username"]
|
user_secret = self.model.get_secret(id=data["secret-user"])
|
||||||
database_password = data["password"]
|
secret_data = user_secret.get_content()
|
||||||
|
database_user = secret_data["username"]
|
||||||
|
database_password = secret_data["password"]
|
||||||
database_type = "mysql+pymysql"
|
database_type = "mysql+pymysql"
|
||||||
has_tls = data.get("tls")
|
has_tls = data.get("tls")
|
||||||
tls_ca = data.get("tls-ca")
|
tls_ca = data.get("tls-ca")
|
||||||
|
@ -464,12 +464,15 @@ def add_base_db_relation(harness: Harness) -> str:
|
|||||||
|
|
||||||
def add_db_relation_credentials(harness: Harness, rel_id: str) -> None:
|
def add_db_relation_credentials(harness: Harness, rel_id: str) -> None:
|
||||||
"""Add db credentials data to db relation."""
|
"""Add db credentials data to db relation."""
|
||||||
|
secret_id = harness.add_model_secret(
|
||||||
|
"mysql", {"username": "foo", "password": "hardpassword"}
|
||||||
|
)
|
||||||
|
harness.grant_secret(secret_id, harness.charm.app.name)
|
||||||
harness.update_relation_data(
|
harness.update_relation_data(
|
||||||
rel_id,
|
rel_id,
|
||||||
"mysql",
|
"mysql",
|
||||||
{
|
{
|
||||||
"username": "foo",
|
"secret-user": secret_id,
|
||||||
"password": "hardpassword",
|
|
||||||
"endpoints": "10.0.0.10",
|
"endpoints": "10.0.0.10",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo "INFO: Fetching libs from charmhub."
|
echo "INFO: Fetching libs from charmhub."
|
||||||
# charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
|
# charmcraft fetch-lib charms.data_platform_libs.v0.data_interfaces
|
||||||
# charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
# charmcraft fetch-lib charms.keystone_k8s.v1.identity_service
|
||||||
# charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq
|
# charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq
|
||||||
# charmcraft fetch-lib charms.traefik_k8s.v1.ingress
|
# charmcraft fetch-lib charms.traefik_k8s.v1.ingress
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,537 +0,0 @@
|
|||||||
# Copyright 2023 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
r"""[DEPRECATED] Relation 'requires' side abstraction for database relation.
|
|
||||||
|
|
||||||
This library is a uniform interface to a selection of common database
|
|
||||||
metadata, with added custom events that add convenience to database management,
|
|
||||||
and methods to consume the application related data.
|
|
||||||
|
|
||||||
Following an example of using the DatabaseCreatedEvent, in the context of the
|
|
||||||
application charm code:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
|
||||||
DatabaseCreatedEvent,
|
|
||||||
DatabaseRequires,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ApplicationCharm(CharmBase):
|
|
||||||
# Application charm that connects to database charms.
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
|
|
||||||
# Charm events defined in the database requires charm library.
|
|
||||||
self.database = DatabaseRequires(self, relation_name="database", database_name="database")
|
|
||||||
self.framework.observe(self.database.on.database_created, self._on_database_created)
|
|
||||||
|
|
||||||
def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start application with rendered configuration
|
|
||||||
self._start_application(config_file)
|
|
||||||
|
|
||||||
# Set active status
|
|
||||||
self.unit.status = ActiveStatus("received database credentials")
|
|
||||||
```
|
|
||||||
|
|
||||||
As shown above, the library provides some custom events to handle specific situations,
|
|
||||||
which are listed below:
|
|
||||||
|
|
||||||
— database_created: event emitted when the requested database is created.
|
|
||||||
— endpoints_changed: event emitted when the read/write endpoints of the database have changed.
|
|
||||||
— read_only_endpoints_changed: event emitted when the read-only endpoints of the database
|
|
||||||
have changed. Event is not triggered if read/write endpoints changed too.
|
|
||||||
|
|
||||||
If it is needed to connect multiple database clusters to the same relation endpoint
|
|
||||||
the application charm can implement the same code as if it would connect to only
|
|
||||||
one database cluster (like the above code example).
|
|
||||||
|
|
||||||
To differentiate multiple clusters connected to the same relation endpoint
|
|
||||||
the application charm can use the name of the remote application:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Get the remote app name of the cluster that triggered this event
|
|
||||||
cluster = event.relation.app.name
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to provide an alias for each different database cluster/relation.
|
|
||||||
|
|
||||||
So, it is possible to differentiate the clusters in two ways.
|
|
||||||
The first is to use the remote application name, i.e., `event.relation.app.name`, as above.
|
|
||||||
|
|
||||||
The second way is to use different event handlers to handle each cluster events.
|
|
||||||
The implementation would be something like the following code:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
|
||||||
DatabaseCreatedEvent,
|
|
||||||
DatabaseRequires,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ApplicationCharm(CharmBase):
|
|
||||||
# Application charm that connects to database charms.
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
|
|
||||||
# Define the cluster aliases and one handler for each cluster database created event.
|
|
||||||
self.database = DatabaseRequires(
|
|
||||||
self,
|
|
||||||
relation_name="database",
|
|
||||||
database_name="database",
|
|
||||||
relations_aliases = ["cluster1", "cluster2"],
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.database.on.cluster1_database_created, self._on_cluster1_database_created
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.database.on.cluster2_database_created, self._on_cluster2_database_created
|
|
||||||
)
|
|
||||||
|
|
||||||
def _on_cluster1_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database on the cluster named cluster1
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
...
|
|
||||||
|
|
||||||
def _on_cluster2_database_created(self, event: DatabaseCreatedEvent) -> None:
|
|
||||||
# Handle the created database on the cluster named cluster2
|
|
||||||
|
|
||||||
# Create configuration file for app
|
|
||||||
config_file = self._render_app_config_file(
|
|
||||||
event.username,
|
|
||||||
event.password,
|
|
||||||
event.endpoints,
|
|
||||||
)
|
|
||||||
...
|
|
||||||
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from collections import namedtuple
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from ops.charm import (
|
|
||||||
CharmEvents,
|
|
||||||
RelationChangedEvent,
|
|
||||||
RelationEvent,
|
|
||||||
RelationJoinedEvent,
|
|
||||||
)
|
|
||||||
from ops.framework import EventSource, Object
|
|
||||||
from ops.model import Relation
|
|
||||||
|
|
||||||
# The unique Charmhub library identifier, never change it
|
|
||||||
LIBID = "0241e088ffa9440fb4e3126349b2fb62"
|
|
||||||
|
|
||||||
# Increment this major API version when introducing breaking changes
|
|
||||||
LIBAPI = 0
|
|
||||||
|
|
||||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
|
||||||
# to 0 if you are raising the major API version.
|
|
||||||
LIBPATCH = 6
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEvent(RelationEvent):
|
|
||||||
"""Base class for database events."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def endpoints(self) -> Optional[str]:
|
|
||||||
"""Returns a comma separated list of read/write endpoints."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("endpoints")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def password(self) -> Optional[str]:
|
|
||||||
"""Returns the password for the created user."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("password")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def read_only_endpoints(self) -> Optional[str]:
|
|
||||||
"""Returns a comma separated list of read only endpoints."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("read-only-endpoints")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def replset(self) -> Optional[str]:
|
|
||||||
"""Returns the replicaset name.
|
|
||||||
|
|
||||||
MongoDB only.
|
|
||||||
"""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("replset")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tls(self) -> Optional[str]:
|
|
||||||
"""Returns whether TLS is configured."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("tls")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tls_ca(self) -> Optional[str]:
|
|
||||||
"""Returns TLS CA."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("tls-ca")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uris(self) -> Optional[str]:
|
|
||||||
"""Returns the connection URIs.
|
|
||||||
|
|
||||||
MongoDB, Redis, OpenSearch and Kafka only.
|
|
||||||
"""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("uris")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username(self) -> Optional[str]:
|
|
||||||
"""Returns the created username."""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("username")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def version(self) -> Optional[str]:
|
|
||||||
"""Returns the version of the database.
|
|
||||||
|
|
||||||
Version as informed by the database daemon.
|
|
||||||
"""
|
|
||||||
if not self.relation.app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.relation.data[self.relation.app].get("version")
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseCreatedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when a new database is created for use on this relation."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEndpointsChangedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when the read/write endpoints are changed."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseReadOnlyEndpointsChangedEvent(DatabaseEvent):
|
|
||||||
"""Event emitted when the read only endpoints are changed."""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseEvents(CharmEvents):
|
|
||||||
"""Database events.
|
|
||||||
|
|
||||||
This class defines the events that the database can emit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
database_created = EventSource(DatabaseCreatedEvent)
|
|
||||||
endpoints_changed = EventSource(DatabaseEndpointsChangedEvent)
|
|
||||||
read_only_endpoints_changed = EventSource(DatabaseReadOnlyEndpointsChangedEvent)
|
|
||||||
|
|
||||||
|
|
||||||
Diff = namedtuple("Diff", "added changed deleted")
|
|
||||||
Diff.__doc__ = """
|
|
||||||
A tuple for storing the diff between two data mappings.
|
|
||||||
|
|
||||||
— added — keys that were added.
|
|
||||||
— changed — keys that still exist but have new values.
|
|
||||||
— deleted — keys that were deleted.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseRequires(Object):
|
|
||||||
"""Requires-side of the database relation."""
|
|
||||||
|
|
||||||
on = DatabaseEvents() # pyright: ignore [reportGeneralTypeIssues]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
charm,
|
|
||||||
relation_name: str,
|
|
||||||
database_name: str,
|
|
||||||
extra_user_roles: Optional[str] = None,
|
|
||||||
relations_aliases: Optional[List[str]] = None,
|
|
||||||
):
|
|
||||||
"""Manager of database client relations."""
|
|
||||||
super().__init__(charm, relation_name)
|
|
||||||
self.charm = charm
|
|
||||||
self.database = database_name
|
|
||||||
self.extra_user_roles = extra_user_roles
|
|
||||||
self.local_app = self.charm.model.app
|
|
||||||
self.local_unit = self.charm.unit
|
|
||||||
self.relation_name = relation_name
|
|
||||||
self.relations_aliases = relations_aliases
|
|
||||||
self.framework.observe(
|
|
||||||
self.charm.on[relation_name].relation_joined, self._on_relation_joined_event
|
|
||||||
)
|
|
||||||
self.framework.observe(
|
|
||||||
self.charm.on[relation_name].relation_changed, self._on_relation_changed_event
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define custom event names for each alias.
|
|
||||||
if relations_aliases:
|
|
||||||
# Ensure the number of aliases does not exceed the maximum
|
|
||||||
# of connections allowed in the specific relation.
|
|
||||||
relation_connection_limit = self.charm.meta.requires[relation_name].limit
|
|
||||||
if len(relations_aliases) != relation_connection_limit:
|
|
||||||
raise ValueError(
|
|
||||||
f"The number of aliases must match the maximum number of connections allowed in the relation. "
|
|
||||||
f"Expected {relation_connection_limit}, got {len(relations_aliases)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for relation_alias in relations_aliases:
|
|
||||||
self.on.define_event(f"{relation_alias}_database_created", DatabaseCreatedEvent)
|
|
||||||
self.on.define_event(
|
|
||||||
f"{relation_alias}_endpoints_changed", DatabaseEndpointsChangedEvent
|
|
||||||
)
|
|
||||||
self.on.define_event(
|
|
||||||
f"{relation_alias}_read_only_endpoints_changed",
|
|
||||||
DatabaseReadOnlyEndpointsChangedEvent,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _assign_relation_alias(self, relation_id: int) -> None:
|
|
||||||
"""Assigns an alias to a relation.
|
|
||||||
|
|
||||||
This function writes in the unit data bag.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
"""
|
|
||||||
# If no aliases were provided, return immediately.
|
|
||||||
if not self.relations_aliases:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Return if an alias was already assigned to this relation
|
|
||||||
# (like when there are more than one unit joining the relation).
|
|
||||||
if (
|
|
||||||
self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
.data[self.local_unit]
|
|
||||||
.get("alias")
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Retrieve the available aliases (the ones that weren't assigned to any relation).
|
|
||||||
available_aliases = self.relations_aliases[:]
|
|
||||||
for relation in self.charm.model.relations[self.relation_name]:
|
|
||||||
alias = relation.data[self.local_unit].get("alias")
|
|
||||||
if alias:
|
|
||||||
logger.debug("Alias %s was already assigned to relation %d", alias, relation.id)
|
|
||||||
available_aliases.remove(alias)
|
|
||||||
|
|
||||||
# Set the alias in the unit relation databag of the specific relation.
|
|
||||||
relation = self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
relation.data[self.local_unit].update({"alias": available_aliases[0]})
|
|
||||||
|
|
||||||
def _diff(self, event: RelationChangedEvent) -> Diff:
|
|
||||||
"""Retrieves the diff of the data in the relation changed databag.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event: relation changed event.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
a Diff instance containing the added, deleted and changed
|
|
||||||
keys from the event relation databag.
|
|
||||||
"""
|
|
||||||
# Retrieve the old data from the data key in the local unit relation databag.
|
|
||||||
old_data = json.loads(event.relation.data[self.local_unit].get("data", "{}"))
|
|
||||||
# Retrieve the new data from the event relation databag.
|
|
||||||
new_data = (
|
|
||||||
{key: value for key, value in event.relation.data[event.app].items() if key != "data"}
|
|
||||||
if event.app
|
|
||||||
else {}
|
|
||||||
)
|
|
||||||
|
|
||||||
# These are the keys that were added to the databag and triggered this event.
|
|
||||||
added = new_data.keys() - old_data.keys()
|
|
||||||
# These are the keys that were removed from the databag and triggered this event.
|
|
||||||
deleted = old_data.keys() - new_data.keys()
|
|
||||||
# These are the keys that already existed in the databag,
|
|
||||||
# but had their values changed.
|
|
||||||
changed = {
|
|
||||||
key for key in old_data.keys() & new_data.keys() if old_data[key] != new_data[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: evaluate the possibility of losing the diff if some error
|
|
||||||
# happens in the charm before the diff is completely checked (DPE-412).
|
|
||||||
# Convert the new_data to a serializable format and save it for a next diff check.
|
|
||||||
event.relation.data[self.local_unit].update({"data": json.dumps(new_data)})
|
|
||||||
|
|
||||||
# Return the diff with all possible changes.
|
|
||||||
return Diff(added, changed, deleted)
|
|
||||||
|
|
||||||
def _emit_aliased_event(self, event: RelationChangedEvent, event_name: str) -> None:
|
|
||||||
"""Emit an aliased event to a particular relation if it has an alias.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
event: the relation changed event that was received.
|
|
||||||
event_name: the name of the event to emit.
|
|
||||||
"""
|
|
||||||
alias = self._get_relation_alias(event.relation.id)
|
|
||||||
if alias:
|
|
||||||
getattr(self.on, f"{alias}_{event_name}").emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_relation_alias(self, relation_id: int) -> Optional[str]:
|
|
||||||
"""Returns the relation alias.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
the relation alias or None if the relation was not found.
|
|
||||||
"""
|
|
||||||
for relation in self.charm.model.relations[self.relation_name]:
|
|
||||||
if relation.id == relation_id:
|
|
||||||
return relation.data[self.local_unit].get("alias")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def fetch_relation_data(self) -> dict:
|
|
||||||
"""Retrieves data from relation.
|
|
||||||
|
|
||||||
This function can be used to retrieve data from a relation
|
|
||||||
in the charm code when outside an event callback.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
a dict of the values stored in the relation data bag
|
|
||||||
for all relation instances (indexed by the relation ID).
|
|
||||||
"""
|
|
||||||
data = {}
|
|
||||||
for relation in self.relations:
|
|
||||||
data[relation.id] = (
|
|
||||||
{key: value for key, value in relation.data[relation.app].items() if key != "data"}
|
|
||||||
if relation.app
|
|
||||||
else {}
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _update_relation_data(self, relation_id: int, data: dict) -> None:
|
|
||||||
"""Updates a set of key-value pairs in the relation.
|
|
||||||
|
|
||||||
This function writes in the application data bag, therefore,
|
|
||||||
only the leader unit can call it.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
relation_id: the identifier for a particular relation.
|
|
||||||
data: dict containing the key-value pairs
|
|
||||||
that should be updated in the relation.
|
|
||||||
"""
|
|
||||||
if self.local_unit.is_leader():
|
|
||||||
relation = self.charm.model.get_relation(self.relation_name, relation_id)
|
|
||||||
relation.data[self.local_app].update(data)
|
|
||||||
|
|
||||||
def _on_relation_joined_event(self, event: RelationJoinedEvent) -> None:
|
|
||||||
"""Event emitted when the application joins the database relation."""
|
|
||||||
# If relations aliases were provided, assign one to the relation.
|
|
||||||
self._assign_relation_alias(event.relation.id)
|
|
||||||
|
|
||||||
# Sets both database and extra user roles in the relation
|
|
||||||
# if the roles are provided. Otherwise, sets only the database.
|
|
||||||
if self.extra_user_roles:
|
|
||||||
self._update_relation_data(
|
|
||||||
event.relation.id,
|
|
||||||
{
|
|
||||||
"database": self.database,
|
|
||||||
"extra-user-roles": self.extra_user_roles,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._update_relation_data(event.relation.id, {"database": self.database})
|
|
||||||
|
|
||||||
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
|
|
||||||
"""Event emitted when the database relation has changed."""
|
|
||||||
# Check which data has changed to emit customs events.
|
|
||||||
diff = self._diff(event)
|
|
||||||
|
|
||||||
# Check if the database is created
|
|
||||||
# (the database charm shared the credentials).
|
|
||||||
if "username" in diff.added and "password" in diff.added:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("database created at %s", datetime.now())
|
|
||||||
getattr(self.on, "database_created").emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "database_created")
|
|
||||||
|
|
||||||
# To avoid unnecessary application restarts do not trigger
|
|
||||||
# “endpoints_changed“ event if “database_created“ is triggered.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Emit an endpoints changed event if the database
|
|
||||||
# added or changed this info in the relation databag.
|
|
||||||
if "endpoints" in diff.added or "endpoints" in diff.changed:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("endpoints changed on %s", datetime.now())
|
|
||||||
getattr(self.on, "endpoints_changed").emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "endpoints_changed")
|
|
||||||
|
|
||||||
# To avoid unnecessary application restarts do not trigger
|
|
||||||
# “read_only_endpoints_changed“ event if “endpoints_changed“ is triggered.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Emit a read only endpoints changed event if the database
|
|
||||||
# added or changed this info in the relation databag.
|
|
||||||
if "read-only-endpoints" in diff.added or "read-only-endpoints" in diff.changed:
|
|
||||||
# Emit the default event (the one without an alias).
|
|
||||||
logger.info("read-only-endpoints changed on %s", datetime.now())
|
|
||||||
getattr(self.on, "read_only_endpoints_changed").emit(
|
|
||||||
event.relation, app=event.app, unit=event.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
# Emit the aliased event (if any).
|
|
||||||
self._emit_aliased_event(event, "read_only_endpoints_changed")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def relations(self) -> List[Relation]:
|
|
||||||
"""The list of Relation instances associated with this relation_name."""
|
|
||||||
return list(self.charm.model.relations[self.relation_name])
|
|
@ -137,8 +137,8 @@ class _TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
|||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -147,7 +147,7 @@ class _TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -54,11 +54,12 @@ class TestJobCtrl(test_utils.CharmTestCase):
|
|||||||
charm_config=test_charms.CHARM_CONFIG,
|
charm_config=test_charms.CHARM_CONFIG,
|
||||||
initial_charm_config=test_charms.INITIAL_CHARM_CONFIG,
|
initial_charm_config=test_charms.INITIAL_CHARM_CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
# clean up events that were dynamically defined,
|
# clean up events that were dynamically defined,
|
||||||
# otherwise we get issues because they'll be redefined,
|
# otherwise we get issues because they'll be redefined,
|
||||||
# which is not allowed.
|
# which is not allowed.
|
||||||
from charms.data_platform_libs.v0.database_requires import (
|
from charms.data_platform_libs.v0.data_interfaces import (
|
||||||
DatabaseEvents,
|
DatabaseRequiresEvents,
|
||||||
)
|
)
|
||||||
|
|
||||||
for attr in (
|
for attr in (
|
||||||
@ -67,9 +68,10 @@ class TestJobCtrl(test_utils.CharmTestCase):
|
|||||||
"database_read_only_endpoints_changed",
|
"database_read_only_endpoints_changed",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
delattr(DatabaseEvents, attr)
|
delattr(DatabaseRequiresEvents, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.addCleanup(self.harness.cleanup)
|
self.addCleanup(self.harness.cleanup)
|
||||||
self.harness.begin()
|
self.harness.begin()
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ applications:
|
|||||||
charm: ch:mysql-k8s
|
charm: ch:mysql-k8s
|
||||||
channel: 8.0/stable
|
channel: 8.0/stable
|
||||||
scale: 1
|
scale: 1
|
||||||
trust: false
|
trust: true
|
||||||
vault:
|
vault:
|
||||||
charm: ch:vault-k8s
|
charm: ch:vault-k8s
|
||||||
channel: latest/edge
|
channel: latest/edge
|
||||||
|
@ -12,7 +12,7 @@ applications:
|
|||||||
charm: ch:mysql-k8s
|
charm: ch:mysql-k8s
|
||||||
channel: 8.0/stable
|
channel: 8.0/stable
|
||||||
scale: 1
|
scale: 1
|
||||||
trust: false
|
trust: true
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
charm: ch:rabbitmq-k8s
|
charm: ch:rabbitmq-k8s
|
||||||
channel: 3.12/edge
|
channel: 3.12/edge
|
||||||
|
@ -12,7 +12,7 @@ applications:
|
|||||||
charm: ch:mysql-k8s
|
charm: ch:mysql-k8s
|
||||||
channel: 8.0/stable
|
channel: 8.0/stable
|
||||||
scale: 1
|
scale: 1
|
||||||
trust: false
|
trust: true
|
||||||
tls-operator:
|
tls-operator:
|
||||||
charm: self-signed-certificates
|
charm: self-signed-certificates
|
||||||
channel: latest/beta
|
channel: latest/beta
|
||||||
|
@ -12,7 +12,7 @@ applications:
|
|||||||
charm: ch:mysql-k8s
|
charm: ch:mysql-k8s
|
||||||
channel: 8.0/stable
|
channel: 8.0/stable
|
||||||
scale: 1
|
scale: 1
|
||||||
trust: false
|
trust: true
|
||||||
ldap-server:
|
ldap-server:
|
||||||
charm: ch:ldap-test-fixture-k8s
|
charm: ch:ldap-test-fixture-k8s
|
||||||
channel: edge
|
channel: edge
|
||||||
|
Loading…
Reference in New Issue
Block a user