Wait for gnocchi service before configuring ceilometer

Currently the ceilometer services are configured and
ceilometer dbsync is run as an action.
A new interface gnocchi_service is introduced that
updates relation data when gnocchi service is up.
Add new relation gnocchi_service in metadata.yaml
as a mandatory relation.
Update dbsync commands so that they are run as part
of configure_charm.
Remove the action ceilometer-upgrade that does the
dbsync as it is no more required.

Change-Id: Iba72250f3d31ef9dafa5b3bd16abefd43a74366a
This commit is contained in:
Hemanth Nakkina 2023-10-10 14:55:38 +05:30
parent 56f17c6d76
commit bf2bdc90b7
9 changed files with 310 additions and 41 deletions

View File

@ -14,11 +14,12 @@ ceilometer-k8s is deployed using below command:
juju deploy ceilometer-k8s ceilometer --trust juju deploy ceilometer-k8s ceilometer --trust
Now connect the ceilometer operator to keystone identity and rabbitmq Now connect the ceilometer operator to keystone identity, rabbitmq
operators: and gnocchi operators:
juju relate keystone:identity-service ceilometer:identity-service juju relate keystone:identity-service ceilometer:identity-service
juju relate rabbitmq:amqp ceilometer:amqp juju relate rabbitmq:amqp ceilometer:amqp
juju relate gnocchi:gnocchi-service ceilometer:gnocchi-db
### Configuration ### Configuration
@ -40,12 +41,13 @@ ceilometer-k8s requires the following relations:
`identity-service`: To register endpoints in Keystone `identity-service`: To register endpoints in Keystone
`amqp`: To connect to Rabbitmq `amqp`: To connect to Rabbitmq
`gnocchi-db`: To connect to Gnocchi database
## OCI Images ## OCI Images
The charm by default uses following images: The charm by default uses following images:
ghcr.io/canonical/ceilometer-consolidated:2023.1 ghcr.io/canonical/ceilometer-consolidated:2023.2
## Contributing ## Contributing

View File

@ -1,7 +1,2 @@
# Copyright 2023 Canonical Ltd. # NOTE: no actions yet!
# See LICENSE file for licensing details. { }
ceilometer-upgrade:
description: |
Perform ceilometer-upgrade. This action will update Ceilometer data store
configuration.

View File

@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
echo "INFO: Fetching libs from charmhub." echo "INFO: Fetching libs from charmhub."
charmcraft fetch-lib charms.gnocchi_k8s.v0.gnocchi_service
# charmcraft fetch-lib charms.data_platform_libs.v0.database_requires # charmcraft fetch-lib charms.data_platform_libs.v0.database_requires
# 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

View 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 = 2
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 if gnocchi service is ready or not."""
is_ready = self.get_remote_app_data("ready")
if is_ready:
return json.loads(is_ready)
return False

View File

@ -47,6 +47,8 @@ requires:
identity-credentials: identity-credentials:
interface: keystone-credentials interface: keystone-credentials
limit: 1 limit: 1
gnocchi-db:
interface: gnocchi
peers: peers:
peers: peers:

View File

@ -25,6 +25,7 @@ from typing import (
List, List,
) )
import ops.charm
import ops.framework import ops.framework
import ops_sunbeam.charm as sunbeam_charm import ops_sunbeam.charm as sunbeam_charm
import ops_sunbeam.container_handlers as sunbeam_chandlers import ops_sunbeam.container_handlers as sunbeam_chandlers
@ -34,13 +35,19 @@ from charms.ceilometer_k8s.v0.ceilometer_service import (
CeilometerConfigRequestEvent, CeilometerConfigRequestEvent,
CeilometerServiceProvides, CeilometerServiceProvides,
) )
from charms.gnocchi_k8s.v0.gnocchi_service import (
GnocchiServiceRequires,
)
from ops.charm import ( from ops.charm import (
ActionEvent,
CharmBase, CharmBase,
RelationEvent,
) )
from ops.main import ( from ops.main import (
main, main,
) )
from ops.model import (
BlockedStatus,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -48,6 +55,71 @@ CEILOMETER_CENTRAL_CONTAINER = "ceilometer-central"
CEILOMETER_NOTIFICATION_CONTAINER = "ceilometer-notification" CEILOMETER_NOTIFICATION_CONTAINER = "ceilometer-notification"
class GnocchiServiceRequiresHandler(sunbeam_rhandlers.RelationHandler):
"""Handle gnocchi service relation on the requires side."""
def __init__(
self,
charm: CharmBase,
relation_name: str,
callback_f: Callable,
mandatory: bool = False,
):
"""Create a new gnocchi service handler.
Create a new GnocchiServiceRequiresHandler that handles initial
events from the relation and invokes the provided callbacks based on
the event raised.
: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
:param mandatory: If the relation is mandatory to proceed with
configuring charm
:type mandatory: bool
"""
super().__init__(charm, relation_name, callback_f, mandatory)
def setup_event_handler(self) -> ops.charm.Object:
"""Configure event handlers for Gnocchi service relation."""
logger.debug("Setting up Gnocchi service event handler")
svc = GnocchiServiceRequires(
self.charm,
self.relation_name,
)
self.framework.observe(
svc.on.readiness_changed,
self._on_gnocchi_service_readiness_changed,
)
self.framework.observe(
svc.on.goneaway,
self._on_gnocchi_service_goneaway,
)
return svc
def _on_gnocchi_service_readiness_changed(
self, event: RelationEvent
) -> None:
"""Handle config_changed event."""
logger.debug("Gnocchi service readiness changed event received")
self.callback_f(event)
def _on_gnocchi_service_goneaway(self, event: RelationEvent) -> None:
"""Handle gone_away event."""
logger.debug("Gnocchi service gone away event received")
self.callback_f(event)
if self.mandatory:
self.status.set(BlockedStatus("integration missing"))
@property
def ready(self) -> bool:
"""Whether handler is ready for use."""
return self.interface.service_ready
class CeilometerServiceProvidesHandler(sunbeam_rhandlers.RelationHandler): class CeilometerServiceProvidesHandler(sunbeam_rhandlers.RelationHandler):
"""Handler for ceilometer service relation.""" """Handler for ceilometer service relation."""
@ -177,13 +249,11 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
service_name = "ceilometer" service_name = "ceilometer"
shared_metering_secret_key = "shared-metering-secret" shared_metering_secret_key = "shared-metering-secret"
mandatory_relations = {"amqp", "identity-credentials"} db_sync_cmds = [["ceilometer-upgrade"]]
mandatory_relations = {"amqp", "identity-credentials", "gnocchi-db"}
def __init__(self, framework: ops.framework): def __init__(self, framework: ops.framework):
super().__init__(framework) super().__init__(framework)
self.framework.observe(
self.on.ceilometer_upgrade_action, self._ceilometer_upgrade_action
)
def get_shared_meteringsecret(self): def get_shared_meteringsecret(self):
"""Return the shared metering secret.""" """Return the shared metering secret."""
@ -210,6 +280,11 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
return return
super().configure_charm(event) super().configure_charm(event)
@property
def db_sync_container_name(self) -> str:
"""Name of Containerto run db sync from."""
return CEILOMETER_NOTIFICATION_CONTAINER
@property @property
def service_user(self) -> str: def service_user(self) -> str:
"""Service user file and directory ownership.""" """Service user file and directory ownership."""
@ -266,6 +341,14 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
self.set_config_from_event, self.set_config_from_event,
) )
handlers.append(self.config_svc) handlers.append(self.config_svc)
if self.can_add_handler("gnocchi-db", handlers):
self.gnocchi_svc = GnocchiServiceRequiresHandler(
self,
"gnocchi-db",
self.configure_charm,
"gnocchi-db" in self.mandatory_relations,
)
handlers.append(self.gnocchi_svc)
return super().get_relation_handlers(handlers) return super().get_relation_handlers(handlers)
@ -289,30 +372,6 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
else: else:
logging.debug("Telemetry secret not yet set, not sending config") logging.debug("Telemetry secret not yet set, not sending config")
def _ceilometer_upgrade_action(self, event: ActionEvent) -> None:
"""Run ceilometer-upgrade.
This action will upgrade the data store configuration in gnocchi.
"""
try:
logger.info("Syncing database...")
cmd = ["ceilometer-upgrade"]
container = self.unit.get_container(
CEILOMETER_NOTIFICATION_CONTAINER
)
process = container.exec(cmd, timeout=5 * 60)
out, warnings = process.wait_output()
logging.debug("Output from database sync: \n%s", out)
if warnings:
for line in warnings.splitlines():
logger.warning("DB Sync Out: %s", line.strip())
event.fail(f"Error in running ceilometer-upgrade: {warnings}")
else:
event.set_results({"message": "ceilometer-upgrade successful"})
except Exception as e:
logger.exception(e)
event.fail(f"Error in running ceilometer-updgrade: {e}")
if __name__ == "__main__": if __name__ == "__main__":
main(CeilometerOperatorCharm) main(CeilometerOperatorCharm)

View File

@ -93,3 +93,5 @@ relations:
- ceilometer:amqp - ceilometer:amqp
- - keystone:identity-credentials - - keystone:identity-credentials
- ceilometer:identity-credentials - ceilometer:identity-credentials
- - gnocchi:gnocchi-service
- ceilometer:gnocchi-db

View File

@ -33,8 +33,8 @@ target_deploy_status:
workload-status: active workload-status: active
workload-status-message-regex: '^.*$' workload-status-message-regex: '^.*$'
ceilometer: ceilometer:
workload-status: active workload-status: waiting
workload-status-message-regex: '^.*$' workload-status-message-regex: '^.*Not all relations are ready$'
aodh: aodh:
workload-status: active workload-status: active
workload-status-message-regex: '^.*$' workload-status-message-regex: '^.*$'

View File

@ -60,6 +60,9 @@ class TestCeilometerOperatorCharm(test_utils.CharmTestCase):
test_utils.set_all_pebbles_ready(self.harness) test_utils.set_all_pebbles_ready(self.harness)
test_utils.add_complete_identity_credentials_relation(self.harness) test_utils.add_complete_identity_credentials_relation(self.harness)
test_utils.add_complete_amqp_relation(self.harness) test_utils.add_complete_amqp_relation(self.harness)
self.harness.add_relation(
"gnocchi-db", "gnocchi", app_data={"ready": "true"}
)
for c in ["ceilometer-central", "ceilometer-notification"]: for c in ["ceilometer-central", "ceilometer-notification"]:
self.check_file(c, "/etc/ceilometer/ceilometer.conf") self.check_file(c, "/etc/ceilometer/ceilometer.conf")