Add interface to provide service readiness
Ceilometer requires gnocchi service to be up and running to perform db sync. Add a new interface gnocchi-service that updates the service readiness on the relation. Update Gnocchi charm to handle the events from the new interface gnocchi-service. Change-Id: I8ea006c1adf8d6839be6915b8389b700692601d6
This commit is contained in:
parent
01f744689a
commit
244fe0e18b
205
charms/gnocchi-k8s/lib/charms/gnocchi_k8s/v0/gnocchi_service.py
Normal file
205
charms/gnocchi-k8s/lib/charms/gnocchi_k8s/v0/gnocchi_service.py
Normal file
@ -0,0 +1,205 @@
|
||||
"""GnocchiService Provides and Requires module.
|
||||
|
||||
This library contains the Requires and Provides classes for handling
|
||||
the Gnocchi service interface.
|
||||
|
||||
Import `GnocchiServiceRequires` in your charm, with the charm object and the
|
||||
relation name:
|
||||
- self
|
||||
- "gnocchi-db"
|
||||
|
||||
Two events are also available to respond to:
|
||||
- readiness_changed
|
||||
- goneaway
|
||||
|
||||
A basic example showing the usage of this relation follows:
|
||||
|
||||
```
|
||||
from charms.gnocchi_k8s.v0.gnocchi_service import (
|
||||
GnocchiServiceRequires
|
||||
)
|
||||
|
||||
class GnocchiServiceClientCharm(CharmBase):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
# GnocchiService Requires
|
||||
self.gnocchi_svc = GnocchiServiceRequires(
|
||||
self, "gnocchi-db",
|
||||
)
|
||||
self.framework.observe(
|
||||
self.gnocchi_svc.on.readiness_changed,
|
||||
self._on_gnocchi_service_readiness_changed
|
||||
)
|
||||
self.framework.observe(
|
||||
self.gnocchi_svc.on.goneaway,
|
||||
self._on_gnocchi_service_goneaway
|
||||
)
|
||||
|
||||
def _on_gnocchi_service_readiness_changed(self, event):
|
||||
'''React to the Gnocchi service readiness changed event.
|
||||
|
||||
This event happens when Gnocchi service relation is added to the
|
||||
model and relation data is changed.
|
||||
'''
|
||||
# Do something with the configuration provided by relation.
|
||||
pass
|
||||
|
||||
def _on_gnocchi_service_goneaway(self, event):
|
||||
'''React to the Gnocchi Service goneaway event.
|
||||
|
||||
This event happens when Gnocchi service relation is removed.
|
||||
'''
|
||||
# HeatSharedConfig Relation has goneaway.
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
from ops.charm import (
|
||||
CharmBase,
|
||||
RelationBrokenEvent,
|
||||
RelationChangedEvent,
|
||||
RelationEvent,
|
||||
)
|
||||
from ops.framework import (
|
||||
EventSource,
|
||||
Object,
|
||||
ObjectEvents,
|
||||
)
|
||||
from ops.model import (
|
||||
Relation,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# The unique Charmhub library identifier, never change it
|
||||
LIBID = "97b7682b415040f3b32d77fff8d93e7e"
|
||||
|
||||
# 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 = 1
|
||||
|
||||
|
||||
class GnocchiServiceReadinessRequestEvent(RelationEvent):
|
||||
"""GnocchiServiceReadinessRequest Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GnocchiServiceProviderEvents(ObjectEvents):
|
||||
"""Events class for `on`."""
|
||||
|
||||
service_readiness = EventSource(GnocchiServiceReadinessRequestEvent)
|
||||
|
||||
|
||||
class GnocchiServiceProvides(Object):
|
||||
"""GnocchiServiceProvides class."""
|
||||
|
||||
on = GnocchiServiceProviderEvents()
|
||||
|
||||
def __init__(self, charm: CharmBase, relation_name: str):
|
||||
super().__init__(charm, relation_name)
|
||||
self.charm = charm
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_changed,
|
||||
self._on_relation_changed,
|
||||
)
|
||||
|
||||
def _on_relation_changed(self, event: RelationChangedEvent):
|
||||
"""Handle Gnocchi service relation changed."""
|
||||
logging.debug("Gnocchi Service relation changed")
|
||||
self.on.service_readiness.emit(event.relation)
|
||||
|
||||
def set_service_status(self, relation: Relation, is_ready: bool) -> None:
|
||||
"""Set gnocchi service readiness status on the relation."""
|
||||
if not self.charm.unit.is_leader():
|
||||
logging.debug("Not a leader unit, skipping setting ready status")
|
||||
return
|
||||
|
||||
logging.debug(
|
||||
f"Setting ready status on relation {relation.app.name} "
|
||||
f"{relation.name}/{relation.id}"
|
||||
)
|
||||
relation.data[self.charm.app]["ready"] = json.dumps(is_ready)
|
||||
|
||||
|
||||
class GnocchiServiceReadinessChangedEvent(RelationEvent):
|
||||
"""GnocchiServiceReadinessChanged Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GnocchiServiceGoneAwayEvent(RelationEvent):
|
||||
"""GnocchiServiceGoneAway Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GnocchiServiceRequirerEvents(ObjectEvents):
|
||||
"""Events class for `on`."""
|
||||
|
||||
readiness_changed = EventSource(GnocchiServiceReadinessChangedEvent)
|
||||
goneaway = EventSource(GnocchiServiceGoneAwayEvent)
|
||||
|
||||
|
||||
class GnocchiServiceRequires(Object):
|
||||
"""GnocchiServiceRequires class."""
|
||||
|
||||
on = GnocchiServiceRequirerEvents()
|
||||
|
||||
def __init__(self, charm: CharmBase, relation_name: str):
|
||||
super().__init__(charm, relation_name)
|
||||
self.charm = charm
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_changed,
|
||||
self._on_relation_changed,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_broken,
|
||||
self._on_relation_broken,
|
||||
)
|
||||
|
||||
def _on_relation_changed(self, event: RelationChangedEvent):
|
||||
"""Handle Gnocchi Service relation changed."""
|
||||
logging.debug("Gnocchi service readiness data changed")
|
||||
self.on.readiness_changed.emit(event.relation)
|
||||
|
||||
def _on_relation_broken(self, event: RelationBrokenEvent):
|
||||
"""Handle Gnocchi Service relation broken."""
|
||||
logging.debug("Gnocchi service on_broken")
|
||||
self.on.goneaway.emit(event.relation)
|
||||
|
||||
@property
|
||||
def _gnocchi_service_rel(self) -> Optional[Relation]:
|
||||
"""The gnocchi service relation."""
|
||||
return self.framework.model.get_relation(self.relation_name)
|
||||
|
||||
def get_remote_app_data(self, key: str) -> Optional[str]:
|
||||
"""Return the value for the given key from remote app data."""
|
||||
if self._gnocchi_service_rel:
|
||||
data = self._gnocchi_service_rel.data[
|
||||
self._gnocchi_service_rel.app
|
||||
]
|
||||
return data.get(key)
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def service_ready(self) -> bool:
|
||||
"""Return the auth_encryption_key."""
|
||||
is_ready = self.get_remote_app_data("ready")
|
||||
if is_ready:
|
||||
return json.loads(is_ready)
|
||||
|
||||
return False
|
@ -52,6 +52,10 @@ requires:
|
||||
ceph:
|
||||
interface: ceph-client
|
||||
|
||||
provides:
|
||||
gnocchi-service:
|
||||
interface: gnocchi
|
||||
|
||||
peers:
|
||||
peers:
|
||||
interface: gnocchi-peer
|
||||
|
@ -20,6 +20,7 @@ This charm provide Gnocchi services as part of an OpenStack deployment
|
||||
|
||||
import logging
|
||||
from typing import (
|
||||
Callable,
|
||||
List,
|
||||
)
|
||||
|
||||
@ -30,7 +31,16 @@ import ops_sunbeam.container_handlers as sunbeam_chandlers
|
||||
import ops_sunbeam.core as sunbeam_core
|
||||
import ops_sunbeam.guard as sunbeam_guard
|
||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
||||
from charms.gnocchi_k8s.v0.gnocchi_service import (
|
||||
GnocchiServiceProvides,
|
||||
GnocchiServiceReadinessRequestEvent,
|
||||
)
|
||||
from ops.charm import (
|
||||
CharmBase,
|
||||
RelationEvent,
|
||||
)
|
||||
from ops.framework import (
|
||||
EventBase,
|
||||
StoredState,
|
||||
)
|
||||
from ops.main import (
|
||||
@ -43,6 +53,54 @@ GNOCHHI_WSGI_CONTAINER = "gnocchi-api"
|
||||
GNOCCHI_METRICD_CONTAINER = "gnocchi-metricd"
|
||||
|
||||
|
||||
class GnocchiServiceProvidesHandler(sunbeam_rhandlers.RelationHandler):
|
||||
"""Handler for Gnocchi service relation on provider side."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
charm: CharmBase,
|
||||
relation_name: str,
|
||||
callback_f: Callable,
|
||||
):
|
||||
"""Create a new gnocchi service handler.
|
||||
|
||||
Create a new GnocchiServiceProvidesHandler that updates service
|
||||
readiness on the related units.
|
||||
|
||||
:param charm: the Charm class the handler is for
|
||||
:type charm: ops.charm.CharmBase
|
||||
:param relation_name: the relation the handler is bound to
|
||||
:type relation_name: str
|
||||
:param callback_f: the function to call when the nodes are connected
|
||||
:type callback_f: Callable
|
||||
"""
|
||||
super().__init__(charm, relation_name, callback_f)
|
||||
|
||||
def setup_event_handler(self):
|
||||
"""Configure event handlers for Gnocchi service relation."""
|
||||
logger.debug("Setting up Gnocchi service event handler")
|
||||
svc = GnocchiServiceProvides(
|
||||
self.charm,
|
||||
self.relation_name,
|
||||
)
|
||||
self.framework.observe(
|
||||
svc.on.service_readiness,
|
||||
self._on_service_readiness,
|
||||
)
|
||||
return svc
|
||||
|
||||
def _on_service_readiness(
|
||||
self, event: GnocchiServiceReadinessRequestEvent
|
||||
) -> None:
|
||||
"""Handle service readiness request event."""
|
||||
self.callback_f(event)
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
"""Report if relation is ready."""
|
||||
return True
|
||||
|
||||
|
||||
class GnocchiWSGIPebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
||||
"""Pebble handler for Gnocchi WSGI services."""
|
||||
|
||||
@ -173,6 +231,18 @@ class GnocchiOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
f"http://localhost:{self.default_public_ingress_port}/healthcheck"
|
||||
)
|
||||
|
||||
def get_relation_handlers(self) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||
"""Relation handlers for the service."""
|
||||
handlers = super().get_relation_handlers()
|
||||
self.svc_ready_handler = GnocchiServiceProvidesHandler(
|
||||
self,
|
||||
"gnocchi-service",
|
||||
self.handle_readiness_request_from_event,
|
||||
)
|
||||
handlers.append(self.svc_ready_handler)
|
||||
|
||||
return handlers
|
||||
|
||||
def get_pebble_handlers(
|
||||
self,
|
||||
) -> List[sunbeam_chandlers.ServicePebbleHandler]:
|
||||
@ -212,6 +282,31 @@ class GnocchiOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
),
|
||||
]
|
||||
|
||||
def configure_app_leader(self, event: EventBase):
|
||||
"""Run global app setup.
|
||||
|
||||
These are tasks that should only be run once per application and only
|
||||
the leader runs them.
|
||||
"""
|
||||
super().configure_app_leader(event)
|
||||
self.set_readiness_on_related_units()
|
||||
|
||||
def handle_readiness_request_from_event(
|
||||
self, event: RelationEvent
|
||||
) -> None:
|
||||
"""Set service readiness in relation data."""
|
||||
self.svc_ready_handler.interface.set_service_status(
|
||||
event.relation, self.bootstrapped()
|
||||
)
|
||||
|
||||
def set_readiness_on_related_units(self) -> None:
|
||||
"""Set service readiness on gnocchi-service related units."""
|
||||
logger.debug(
|
||||
"Set service readiness on all connected gnocchi-service relations"
|
||||
)
|
||||
for relation in self.framework.model.relations["gnocchi-service"]:
|
||||
self.svc_ready_handler.interface.set_service_status(relation, True)
|
||||
|
||||
|
||||
class GnocchiCephOperatorCharm(GnocchiOperatorCharm):
|
||||
"""Charm the Gnocchi service with Ceph backend."""
|
||||
|
@ -85,6 +85,7 @@ class TestGnocchiCephOperatorCharm(test_utils.CharmTestCase):
|
||||
self.harness.update_relation_data(
|
||||
ceph_rel_id, "ceph-mon/0", {"ingress-address": "10.0.0.33"}
|
||||
)
|
||||
self.harness.add_relation("gnocchi-service", "ceilometer", app_data={})
|
||||
test_utils.add_ceph_relation_credentials(self.harness, ceph_rel_id)
|
||||
test_utils.add_db_relation_credentials(
|
||||
self.harness, test_utils.add_base_db_relation(self.harness)
|
||||
|
@ -104,7 +104,7 @@ deps =
|
||||
isort
|
||||
codespell
|
||||
commands =
|
||||
codespell {[vars]all_path}
|
||||
codespell {[vars]all_path}
|
||||
# pflake8 wrapper supports config from pyproject.toml
|
||||
pflake8 --exclude {[vars]lib_path} --config {toxinidir}/pyproject.toml {[vars]all_path}
|
||||
isort --check-only --diff {[vars]all_path} --skip-glob {[vars]lib_path}
|
||||
|
Loading…
x
Reference in New Issue
Block a user