Skip to content

Commit

Permalink
DPE-1697: Add gloss: charm metadata/contribution updated, add docs (#13)
Browse files Browse the repository at this point in the history
* DPE-1697: Add gloss: charm metadata/contribution updated, add docs

* Fixup

* Update libs to make tests happy
  • Loading branch information
taurus-forever authored Sep 1, 2023
1 parent 575f142 commit 1fba231
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 116 deletions.
43 changes: 40 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
# Contributing

## Overview

To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup).

This documents explains the processes and practices recommended for contributing enhancements to
this operator.

- Generally, before developing enhancements to this charm, you should consider [opening an issue
](https://github.com/canonical/mysql-test-app/issues) explaining your use case.
- If you would like to chat with us about your use-cases or proposed implementation, you can reach
us at [public Canonical Data Platform channel](https://chat.charmhub.io/charmhub/channels/data-platform)
or [Discourse](https://discourse.charmhub.io/).
- Familiarising yourself with the [Charmed Operator Framework](https://juju.is/docs/sdk) library
will help you a lot when working on new features or bug fixes.
- All enhancements require review before being merged. Code review typically examines
- code quality
- test coverage
- user experience for Juju administrators of this charm.
- Please help us out in ensuring easy to review branches by rebasing your pull request branch onto
the `main` branch. This also avoids merge commits and creates a linear Git commit history.

## Developing

You can use the environments created by `tox` for development:

```shell
Expand All @@ -17,9 +38,6 @@ that can be used for linting and formatting code when you're preparing contribut
```shell
tox -e fmt # update your code according to linting rules
tox -e lint # code style
tox -e unit # unit tests
tox -e integration # integration tests
tox # runs 'lint' and 'unit' environments
```

## Build the charm
Expand All @@ -29,3 +47,22 @@ Build the charm in this git repository using:
```shell
charmcraft pack
```

### Deploy

```bash
# Create a model
juju add-model dev

# Enable DEBUG logging
juju model-config logging-config="<root>=INFO;unit=DEBUG"

# Deploy the charm
juju deploy ./mysql-test-app_ubuntu-22.04-amd64.charm
```

## Canonical Contributor Agreement

Canonical welcomes contributions to the MySQL Test App. Please check out our
[contributor agreement](https://ubuntu.com/legal/contributors)if you're
interested in contributing to the solution.
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# MySQL Test Application
[![None](https://charmhub.io/mysql-test-app/badge.svg)](https://charmhub.io/mysql-test-app)

MySQL stack tester charm - this is a simple application used exclusively for integrations test of
[mysql-k8s][mysql-k8s], [mysql][mysql], [mysql-router-k8s][mysql-router-k8s],
[mysql-router][mysql-router], [mysql-bundle-k8s][mysql-bundle-k8s] and
[mysql-bundle][mysql-bundle].
MySQL test charm - is a simple application used exclusively for various tests of
various tests of "Charmed MySQL" charms (see the "References" section).

## Relations

This charm implements relations interfaces:
This charm implements [relations interfaces](https://charmhub.io/mysql-k8s/docs/e-interfaces):
* database
* mysql (legacy)

The list of available endpoints is [here](https://charmhub.io/mysql-test-app/integrations).

On using the `mysql` legacy relation interface with either [mysql] or [mysql-k8s] charms, its
necessary to config the database name with:

Expand All @@ -20,17 +19,29 @@ necessary to config the database name with:
```

## Actions

Actions are listed on [actions page](https://charmhub.io/mysql-test-app/actions)

## References
* [MySQL Test App](https://charmhub.io/mysql-test-app)
* [mysql-k8s](https://charmhub.io/mysql-k8s)
* [mysql](https://charmhub.io/mysql)
* [mysql-router-k8s](https://charmhub.io/mysql-router-k8s)
* [mysql-router](https://charmhub.io/mysql-router?channel=dpe/edge)
* [mysql-bundle-k8s](https://charmhub.io/mysql-bundle-k8s)
* [mysql-bundle](https://charmhub.io/mysql-bundle)
* [MySQL Test App at Charmhub](https://charmhub.io/mysql-test-app)
* [PostgreSQL Test App](https://charmhub.io/postgresql-test-app)

## Security
Security issues in the MySQL Test App can be reported through [LaunchPad](https://wiki.ubuntu.com/DebuggingSecurity#How%20to%20File). Please do not file GitHub issues about security issues.

[mysql-k8s]: https://charmhub.io/mysql-k8s
[mysql]: https://charmhub.io/mysql
[mysql-router-k8s]: https://charmhub.io/mysql-router-k8s
[mysql-router]: https://charmhub.io/mysql-router?channel=dpe/edge
[mysql-bundle-k8s]: https://charmhub.io/mysql-bundle-k8s
[mysql-bundle]: https://charmhub.io/mysql-bundle
## Contributing
Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines on enhancements to this charm following best practice guidelines, and [CONTRIBUTING.md](https://github.com/canonical/mysql-test-app/blob/main/CONTRIBUTING.md) for developer guidance.

## References
## License
The MySQL Test App [is distributed](https://github.com/canonical/mysql-test-app/blob/main/LICENSE) under the Apache Software License, version 2.0.
It installs/operates/depends on [MySQL Community Edition](https://github.com/mysql/mysql-server), which [is licensed](https://github.com/mysql/mysql-server/blob/8.0/LICENSE) under the GPL License, version 2.

* [MySQL Test App at Charmhub](https://charmhub.io/mysql-test-app)
## Trademark Notice
MySQL is a trademark or registered trademark of Oracle America, Inc.
Other trademarks are property of their respective owners.
148 changes: 54 additions & 94 deletions lib/charms/data_platform_libs/v0/data_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 16
LIBPATCH = 17

PYDEPS = ["ops>=2.0.0"]

Expand Down Expand Up @@ -365,11 +365,11 @@ def diff(event: RelationChangedEvent, bucket: Union[Unit, Application]) -> Diff:
return Diff(added, changed, deleted)


# Base DataProvides and DataRequires
# Base DataRelation


class DataProvides(Object, ABC):
"""Base provides-side of the data products relation."""
class DataRelation(Object, ABC):
"""Base relation data mainpulation class."""

def __init__(self, charm: CharmBase, relation_name: str) -> None:
super().__init__(charm, relation_name)
Expand All @@ -379,23 +379,11 @@ def __init__(self, charm: CharmBase, relation_name: str) -> None:
self.relation_name = relation_name
self.framework.observe(
charm.on[relation_name].relation_changed,
self._on_relation_changed,
self._on_relation_changed_event,
)

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.
"""
return diff(event, self.local_app)

@abstractmethod
def _on_relation_changed(self, event: RelationChangedEvent) -> None:
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
"""Event emitted when the relation data has changed."""
raise NotImplementedError

Expand All @@ -404,10 +392,11 @@ def fetch_relation_data(self) -> dict:
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.
Returns:
a dict of the values stored in the relation data bag
for all relation instances (indexed by the relation id).
for all relation instances (indexed by the relation ID).
"""
data = {}
for relation in self.relations:
Expand All @@ -430,13 +419,49 @@ def _update_relation_data(self, relation_id: int, data: dict) -> None:
that should be updated in the relation.
"""
if self.local_unit.is_leader():
if relation := self.charm.model.get_relation(self.relation_name, relation_id):
relation = self.charm.model.get_relation(self.relation_name, relation_id)
if relation:
relation.data[self.local_app].update(data)

@staticmethod
def _is_relation_active(relation: Relation):
"""Whether the relation is active based on contained data."""
try:
_ = repr(relation.data)
return True
except (RuntimeError, ModelError):
return False

@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])
return [
relation
for relation in self.charm.model.relations[self.relation_name]
if self._is_relation_active(relation)
]


# Base DataProvides and DataRequires


class DataProvides(DataRelation):
"""Base provides-side of the data products relation."""

def __init__(self, charm: CharmBase, relation_name: str) -> None:
super().__init__(charm, relation_name)

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.
"""
return diff(event, self.local_app)

def set_credentials(self, relation_id: int, username: str, password: str) -> None:
"""Set credentials.
Expand Down Expand Up @@ -476,7 +501,7 @@ def set_tls_ca(self, relation_id: int, tls_ca: str) -> None:
self._update_relation_data(relation_id, {"tls-ca": tls_ca})


class DataRequires(Object, ABC):
class DataRequires(DataRelation):
"""Requires-side of the relation."""

def __init__(
Expand All @@ -487,62 +512,16 @@ def __init__(
):
"""Manager of base client relations."""
super().__init__(charm, relation_name)
self.charm = charm
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.framework.observe(
self.charm.on[relation_name].relation_created, self._on_relation_created_event
)
self.framework.observe(
self.charm.on[relation_name].relation_changed, self._on_relation_changed_event
)

@abstractmethod
def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
"""Event emitted when the relation is created."""
raise NotImplementedError

@abstractmethod
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
raise NotImplementedError

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.
Function cannot be used in `*-relation-broken` events and will raise an exception.
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 _diff(self, event: RelationChangedEvent) -> Diff:
"""Retrieves the diff of the data in the relation changed databag.
Expand All @@ -555,23 +534,6 @@ def _diff(self, event: RelationChangedEvent) -> Diff:
"""
return diff(event, self.local_unit)

@property
def relations(self) -> List[Relation]:
"""The list of Relation instances associated with this relation_name."""
return [
relation
for relation in self.charm.model.relations[self.relation_name]
if self._is_relation_active(relation)
]

@staticmethod
def _is_relation_active(relation: Relation):
try:
_ = repr(relation.data)
return True
except (RuntimeError, ModelError):
return False

@staticmethod
def _is_resource_created_for_relation(relation: Relation) -> bool:
if not relation.app:
Expand Down Expand Up @@ -797,7 +759,7 @@ class DatabaseProvides(DataProvides):
def __init__(self, charm: CharmBase, relation_name: str) -> None:
super().__init__(charm, relation_name)

def _on_relation_changed(self, event: RelationChangedEvent) -> None:
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
"""Event emitted when the relation has changed."""
# Only the leader should handle this event.
if not self.local_unit.is_leader():
Expand Down Expand Up @@ -938,11 +900,8 @@ def _assign_relation_alias(self, relation_id: int) -> None:

# 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")
):
relation = self.charm.model.get_relation(self.relation_name, relation_id)
if relation and relation.data[self.local_unit].get("alias"):
return

# Retrieve the available aliases (the ones that weren't assigned to any relation).
Expand All @@ -955,7 +914,8 @@ def _assign_relation_alias(self, relation_id: int) -> None:

# 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]})
if relation:
relation.data[self.local_unit].update({"alias": available_aliases[0]})

def _emit_aliased_event(self, event: RelationChangedEvent, event_name: str) -> None:
"""Emit an aliased event to a particular relation if it has an alias.
Expand Down Expand Up @@ -1197,7 +1157,7 @@ class KafkaProvides(DataProvides):
def __init__(self, charm: CharmBase, relation_name: str) -> None:
super().__init__(charm, relation_name)

def _on_relation_changed(self, event: RelationChangedEvent) -> None:
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
"""Event emitted when the relation has changed."""
# Only the leader should handle this event.
if not self.local_unit.is_leader():
Expand Down Expand Up @@ -1377,7 +1337,7 @@ class OpenSearchProvides(DataProvides):
def __init__(self, charm: CharmBase, relation_name: str) -> None:
super().__init__(charm, relation_name)

def _on_relation_changed(self, event: RelationChangedEvent) -> None:
def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
"""Event emitted when the relation has changed."""
# Only the leader should handle this event.
if not self.local_unit.is_leader():
Expand Down
Loading

0 comments on commit 1fba231

Please sign in to comment.