Data Platform Libs

Channel Revision Published Runs on
latest/stable 26 26 Jun 2023
Ubuntu 22.04
latest/candidate 26 26 Jun 2023
Ubuntu 22.04
latest/edge 46 21 Sep 2023
Ubuntu 22.04
juju deploy data-platform-libs
Show information

Platform:

Ubuntu
22.04

charms.data_platform_libs.v0.data_interfaces

Library to manage the relation for the data-platform products.

This library contains the Requires and Provides classes for handling the relation between an application and multiple managed application supported by the data-team: MySQL, Postgresql, MongoDB, Redis, and Kafka.

Database (MySQL, Postgresql, MongoDB, and Redis)
Requires Charm

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:


from charms.data_platform_libs.v0.data_interfaces 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:


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:


from charms.data_platform_libs.v0.data_interfaces 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,
        )
        ...

When it's needed to check whether a plugin (extension) is enabled on the PostgreSQL charm, you can use the is_postgresql_plugin_enabled method. To use that, you need to add the following dependency to your charmcraft.yaml file:


parts:
  charm:
    charm-binary-python-packages:
      - psycopg[binary]

Provider Charm

Following an example of using the DatabaseRequestedEvent, in the context of the database charm code:

from charms.data_platform_libs.v0.data_interfaces import DatabaseProvides

class SampleCharm(CharmBase):

    def __init__(self, *args):
        super().__init__(*args)
        # Charm events defined in the database provides charm library.
        self.provided_database = DatabaseProvides(self, relation_name="database")
        self.framework.observe(self.provided_database.on.database_requested,
            self._on_database_requested)
        # Database generic helper
        self.database = DatabaseHelper()

    def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
        # Handle the event triggered by a new database requested in the relation
        # Retrieve the database name using the charm library.
        db_name = event.database
        # generate a new user credential
        username = self.database.generate_user()
        password = self.database.generate_password()
        # set the credentials for the relation
        self.provided_database.set_credentials(event.relation.id, username, password)
        # set other variables for the relation event.set_tls("False")

As shown above, the library provides a custom event (database_requested) to handle the situation when an application charm requests a new database to be created. It's preferred to subscribe to this event instead of relation changed event to avoid creating a new database when other information other than a database name is exchanged in the relation databag.

Kafka

This library is the interface to use and interact with the Kafka charm. This library contains custom events that add convenience to manage Kafka, and provides methods to consume the application related data.

Requirer Charm

from charms.data_platform_libs.v0.data_interfaces import (
    BootstrapServerChangedEvent,
    KafkaRequires,
    TopicCreatedEvent,
)

class ApplicationCharm(CharmBase):

    def __init__(self, *args):
        super().__init__(*args)
        self.kafka = KafkaRequires(self, "kafka_client", "test-topic")
        self.framework.observe(
            self.kafka.on.bootstrap_server_changed, self._on_kafka_bootstrap_server_changed
        )
        self.framework.observe(
            self.kafka.on.topic_created, self._on_kafka_topic_created
        )

    def _on_kafka_bootstrap_server_changed(self, event: BootstrapServerChangedEvent):
        # Event triggered when a bootstrap server was changed for this application

        new_bootstrap_server = event.bootstrap_server
        ...

    def _on_kafka_topic_created(self, event: TopicCreatedEvent):
        # Event triggered when a topic was created for this application
        username = event.username
        password = event.password
        tls = event.tls
        tls_ca= event.tls_ca
        bootstrap_server event.bootstrap_server
        consumer_group_prefic = event.consumer_group_prefix
        zookeeper_uris = event.zookeeper_uris
        ...

As shown above, the library provides some custom events to handle specific situations, which are listed below:

  • topic_created: event emitted when the requested topic is created.
  • bootstrap_server_changed: event emitted when the bootstrap server have changed.
  • credential_changed: event emitted when the credentials of Kafka changed.
Provider Charm

Following the previous example, this is an example of the provider charm.

class SampleCharm(CharmBase):

from charms.data_platform_libs.v0.data_interfaces import (
    KafkaProvides,
    TopicRequestedEvent,
)

    def __init__(self, *args):
        super().__init__(*args)

        # Default charm events.
        self.framework.observe(self.on.start, self._on_start)

        # Charm events defined in the Kafka Provides charm library.
        self.kafka_provider = KafkaProvides(self, relation_name="kafka_client")
        self.framework.observe(self.kafka_provider.on.topic_requested, self._on_topic_requested)
        # Kafka generic helper
        self.kafka = KafkaHelper()

    def _on_topic_requested(self, event: TopicRequestedEvent):
        # Handle the on_topic_requested event.

        topic = event.topic
        relation_id = event.relation.id
        # set connection info in the databag relation
        self.kafka_provider.set_bootstrap_server(relation_id, self.kafka.get_bootstrap_server())
        self.kafka_provider.set_credentials(relation_id, username=username, password=password)
        self.kafka_provider.set_consumer_group_prefix(relation_id, ...)
        self.kafka_provider.set_tls(relation_id, "False")
        self.kafka_provider.set_zookeeper_uris(relation_id, ...)

As shown above, the library provides a custom event (topic_requested) to handle the situation when an application charm requests a new topic to be created. It is preferred to subscribe to this event instead of relation changed event to avoid creating a new topic when other information other than a topic name is exchanged in the relation databag.


Index

class SecretGroup

Description

Secret groups as constants. None

class DataInterfacesError

Description

Common ancestor for DataInterfaces related exceptions. None

class SecretError

Description

Common ancestor for Secrets related exceptions. None

class SecretAlreadyExistsError

Description

A secret that was to be added already exists. None

class SecretsUnavailableError

Description

Secrets aren't yet available for Juju version used. None

class SecretsIllegalUpdateError

Description

Secrets aren't yet available for Juju version used. None

def get_encoded_field(
    relation,
    member,
    field
)

Description

Retrieve and decode an encoded field from relation data. None

def set_encoded_field(
    relation,
    member,
    field,
    value
)

Description

Set an encoded field from relation data. None

def diff(
    event: RelationChangedEvent,
    bucket
)

Retrieves the diff of the data in the relation changed databag.

Arguments

event

relation changed event.

bucket

bucket of the databag (app or unit)

Returns

a Diff instance containing the added, deleted and changed keys from the event relation databag.

def leader_only(f)

Description

Decorator to ensure that only leader can perform given operation. None

Methods

leader_only. wrapper( self )

def juju_secrets_only(f)

Description

Decorator to ensure that certain operations would be only executed on Juju3. None

Methods

juju_secrets_only. wrapper( self )

class Scope

Description

Peer relations scope. None

class CachedSecret

Locally cache a secret.

Description

The data structure is precisely re-using/simulating as in the actual Secret Storage

Methods

CachedSecret. __init__( self , charm: CharmBase , label: str , secret_uri )

CachedSecret. add_secret( self , content , relation: Relation )

Description

Create a new secret. None

CachedSecret. meta( self )

Description

Getting cached secret meta-information. None

CachedSecret. get_content( self )

Description

Getting cached secret content. None

CachedSecret. set_content( self , content )

Description

Setting cached secret content. None

CachedSecret. get_info( self )

Description

Wrapper function to apply the corresponding call on the Secret object within CachedSecret if any. None

class SecretCache

Description

A data structure storing CachedSecret objects. None

Methods

SecretCache. __init__( self , charm )

SecretCache. get( self , label: str , uri )

Description

Getting a secret from Juju Secret store or cache. None

SecretCache. add( self , label: str , content , relation: Relation )

Description

Adding a secret to Juju Secret. None

class DataRelation

Description

Base relation data mainpulation (abstract) class. None

Methods

DataRelation. __init__( self , charm: CharmBase , relation_name: str )

DataRelation. relations( self )

Description

The list of Relation instances associated with this relation_name. None

DataRelation. secrets_enabled( self )

Description

Is this Juju version allowing for Secrets usage? None

DataRelation. get_relation( self , relation_name , relation_id )

Description

Safe way of retrieving a relation. None

DataRelation. fetch_relation_data( self , relation_ids , fields , relation_name )

Retrieves data from relation.

Returns

a dict of the values stored in the relation data bag for all relation instances (indexed by the relation ID).

Description

This function can be used to retrieve data from a relation in the charm code when outside an event callback. Function cannot be used in *-relation-broken events and will raise an exception.

DataRelation. update_relation_data( self , relation_id: int , data: dict )

Description

Update the data within the relation. None

class DataProvides

Description

Base provides-side of the data products relation. None

Methods

DataProvides. __init__( self , charm: CharmBase , relation_name: str )

DataProvides. update_relation_data( self , relation_id: int , fields )

Description

Set values for fields not caring whether it's a secret or not. None

DataProvides. set_credentials( self , relation_id: int , username: str , password: str )

Set credentials.

Arguments

relation_id

the identifier for a particular relation.

username

user that was created.

password

password of the created user.

Description

This function writes in the application data bag, therefore, only the leader unit can call it.

DataProvides. set_tls( self , relation_id: int , tls: str )

Set whether TLS is enabled.

Arguments

relation_id

the identifier for a particular relation.

tls

whether tls is enabled (True or False).

DataProvides. set_tls_ca( self , relation_id: int , tls_ca: str )

Set the TLS CA in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

tls_ca

TLS certification authority.

class DataRequires

Description

Requires-side of the relation. None

Methods

DataRequires. __init__( self , charm , relation_name: str , extra_user_roles , additional_secret_fields )

Description

Manager of base client relations. None

DataRequires. secret_fields( self )

Description

Local access to secrets field, in case they are being used. None

DataRequires. is_resource_created( self , relation_id )

Check if the resource has been created.

Arguments

relation_id (int)

When provided the check is done only for the relation id provided, otherwise the check is done for all relations

Returns

True or False

Description

This function can be used to check if the Provider answered with data in the charm code when outside an event callback.

DataRequires. update_relation_data( self , relation_id: int , data: dict )

Updates a set of key-value pairs in the relation.

Arguments

relation_id

the identifier for a particular relation.

data

dict containing the key-value pairs that should be updated in the relation.

Description

This function writes in the application data bag, therefore, only the leader unit can call it.

DataRequires. fetch_relation_field( self , relation_id: int , field: str , relation_name )

Description

Get a single field from the relation data. None

class ExtraRoleEvent

Description

Base class for data events. None

Methods

ExtraRoleEvent. extra_user_roles( self )

Description

Returns the extra user roles that were requested. None

class AuthenticationEvent

Base class for authentication fields for events.

Description

The amount of logic added here is not ideal -- but this was the only way to preserve the interface when moving to Juju Secrets

Methods

AuthenticationEvent. secrets_enabled( self )

Description

Is this Juju version allowing for Secrets usage? None

AuthenticationEvent. username( self )

Description

Returns the created username. None

AuthenticationEvent. password( self )

Description

Returns the password for the created user. None

AuthenticationEvent. tls( self )

Description

Returns whether TLS is configured. None

AuthenticationEvent. tls_ca( self )

Description

Returns TLS CA. None

class DatabaseProvidesEvent

Description

Base class for database events. None

Methods

DatabaseProvidesEvent. database( self )

Description

Returns the database that was requested. None

class DatabaseRequestedEvent

Description

Event emitted when a new database is requested for use on this relation. None

class DatabaseProvidesEvents

Database events.

Description

This class defines the events that the database can emit.

class DatabaseRequiresEvent

Description

Base class for database events. None

Methods

DatabaseRequiresEvent. database( self )

Description

Returns the database name. None

DatabaseRequiresEvent. endpoints( self )

Returns a comma separated list of read/write endpoints.

Description

In VM charms, this is the primary's address. In kubernetes charms, this is the service to the primary pod.

DatabaseRequiresEvent. read_only_endpoints( self )

Returns a comma separated list of read only endpoints.

Description

In VM charms, this is the address of all the secondary instances. In kubernetes charms, this is the service to all replica pod instances.

DatabaseRequiresEvent. replset( self )

Returns the replicaset name.

Description

MongoDB only.

DatabaseRequiresEvent. uris( self )

Returns the connection URIs.

Description

MongoDB, Redis, OpenSearch.

DatabaseRequiresEvent. version( self )

Returns the version of the database.

Description

Version as informed by the database daemon.

class DatabaseCreatedEvent

Description

Event emitted when a new database is created for use on this relation. None

class DatabaseEndpointsChangedEvent

Description

Event emitted when the read/write endpoints are changed. None

class DatabaseReadOnlyEndpointsChangedEvent

Description

Event emitted when the read only endpoints are changed. None

class DatabaseRequiresEvents

Database events.

Description

This class defines the events that the database can emit.

class DatabaseProvides

Description

Provider-side of the database relations. None

Methods

DatabaseProvides. __init__( self , charm: CharmBase , relation_name: str )

DatabaseProvides. set_database( self , relation_id: int , database_name: str )

Set database name.

Arguments

relation_id

the identifier for a particular relation.

database_name

database name.

Description

This function writes in the application data bag, therefore, only the leader unit can call it.

DatabaseProvides. set_endpoints( self , relation_id: int , connection_strings: str )

Set database primary connections.

Arguments

relation_id

the identifier for a particular relation.

connection_strings

database hosts and ports comma separated list.

Description

This function writes in the application data bag, therefore, only the leader unit can call it.

In VM charms, only the primary's address should be passed as an endpoint. In kubernetes charms, the service endpoint to the primary pod should be passed as an endpoint.

DatabaseProvides. set_read_only_endpoints( self , relation_id: int , connection_strings: str )

Set database replicas connection strings.

Arguments

relation_id

the identifier for a particular relation.

connection_strings

database hosts and ports comma separated list.

Description

This function writes in the application data bag, therefore, only the leader unit can call it.

DatabaseProvides. set_replset( self , relation_id: int , replset: str )

Set replica set name in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

replset

replica set name.

Description

MongoDB only.

DatabaseProvides. set_uris( self , relation_id: int , uris: str )

Set the database connection URIs in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

uris

connection URIs.

Description

MongoDB, Redis, and OpenSearch only.

DatabaseProvides. set_version( self , relation_id: int , version: str )

Set the database version in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

version

database version.

class DatabaseRequires

Description

Requires-side of the database relation. None

Methods

DatabaseRequires. __init__( self , charm , relation_name: str , database_name: str , extra_user_roles , relations_aliases , additional_secret_fields )

Description

Manager of database client relations. None

DatabaseRequires. is_postgresql_plugin_enabled( self , plugin: str , relation_index: int )

Returns whether a plugin is enabled in the database.

Arguments

plugin

name of the plugin to check.

relation_index

optional relation index to check the database (default: 0 - first relation).

class KafkaProvidesEvent

Description

Base class for Kafka events. None

Methods

KafkaProvidesEvent. topic( self )

Description

Returns the topic that was requested. None

KafkaProvidesEvent. consumer_group_prefix( self )

Description

Returns the consumer-group-prefix that was requested. None

class TopicRequestedEvent

Description

Event emitted when a new topic is requested for use on this relation. None

class KafkaProvidesEvents

Kafka events.

Description

This class defines the events that the Kafka can emit.

class KafkaRequiresEvent

Description

Base class for Kafka events. None

Methods

KafkaRequiresEvent. topic( self )

Description

Returns the topic. None

KafkaRequiresEvent. bootstrap_server( self )

Description

Returns a comma-separated list of broker uris. None

KafkaRequiresEvent. consumer_group_prefix( self )

Description

Returns the consumer-group-prefix. None

KafkaRequiresEvent. zookeeper_uris( self )

Description

Returns a comma separated list of Zookeeper uris. None

class TopicCreatedEvent

Description

Event emitted when a new topic is created for use on this relation. None

class BootstrapServerChangedEvent

Description

Event emitted when the bootstrap server is changed. None

class KafkaRequiresEvents

Kafka events.

Description

This class defines the events that the Kafka can emit.

class KafkaProvides

Description

Provider-side of the Kafka relation. None

Methods

KafkaProvides. __init__( self , charm: CharmBase , relation_name: str )

KafkaProvides. set_topic( self , relation_id: int , topic: str )

Set topic name in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

topic

the topic name.

KafkaProvides. set_bootstrap_server( self , relation_id: int , bootstrap_server: str )

Set the bootstrap server in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

bootstrap_server

the bootstrap server address.

KafkaProvides. set_consumer_group_prefix( self , relation_id: int , consumer_group_prefix: str )

Set the consumer group prefix in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

consumer_group_prefix

the consumer group prefix string.

KafkaProvides. set_zookeeper_uris( self , relation_id: int , zookeeper_uris: str )

Set the zookeeper uris in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

zookeeper_uris

comma-separated list of ZooKeeper server uris.

class KafkaRequires

Description

Requires-side of the Kafka relation. None

Methods

KafkaRequires. __init__( self , charm , relation_name: str , topic: str , extra_user_roles , consumer_group_prefix , additional_secret_fields )

Description

Manager of Kafka client relations. None

KafkaRequires. topic( self )

Description

Topic to use in Kafka. None

KafkaRequires. topic( self , value )

class OpenSearchProvidesEvent

Description

Base class for OpenSearch events. None

Methods

OpenSearchProvidesEvent. index( self )

Description

Returns the index that was requested. None

class IndexRequestedEvent

Description

Event emitted when a new index is requested for use on this relation. None

class OpenSearchProvidesEvents

OpenSearch events.

Description

This class defines the events that OpenSearch can emit.

class OpenSearchRequiresEvent

Description

Base class for OpenSearch requirer events. None

class IndexCreatedEvent

Description

Event emitted when a new index is created for use on this relation. None

class OpenSearchRequiresEvents

OpenSearch events.

Description

This class defines the events that the opensearch requirer can emit.

class OpenSearchProvides

Description

Provider-side of the OpenSearch relation. None

Methods

OpenSearchProvides. __init__( self , charm: CharmBase , relation_name: str )

OpenSearchProvides. set_index( self , relation_id: int , index: str )

Set the index in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

index

the index as it is created on the provider charm. This needn't match the requested index, and can be used to present a different index name if, for example, the requested index is invalid.

OpenSearchProvides. set_endpoints( self , relation_id: int , endpoints: str )

Set the endpoints in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

endpoints

the endpoint addresses for opensearch nodes.

OpenSearchProvides. set_version( self , relation_id: int , version: str )

Set the opensearch version in the application relation databag.

Arguments

relation_id

the identifier for a particular relation.

version

database version.

class OpenSearchRequires

Description

Requires-side of the OpenSearch relation. None

Methods

OpenSearchRequires. __init__( self , charm , relation_name: str , index: str , extra_user_roles , additional_secret_fields )

Description

Manager of OpenSearch client relations. None