Add support for ceilometer-service relation

Implement ceilometer-service requires part of the relation.
Configure snap-openstack-hypervsor config parameters
telemetry.enable and telemetry.publisher-secret when the
relation is joined/changed.
Configure telemetry.enable to False when ceilometer-service
relation is removed.

Change-Id: I168348aba340db3ec2f63b69acef439906542e63
This commit is contained in:
Hemanth Nakkina 2023-09-01 16:09:31 +05:30
parent 1ac1e494c9
commit b3d4568882
4 changed files with 329 additions and 1 deletions

View File

@ -0,0 +1,224 @@
"""CeilometerServiceProvides and Requires module.
This library contains the Requires and Provides classes for handling
the ceilometer_service interface.
Import `CeilometerServiceRequires` in your charm, with the charm object and the
relation name:
- self
- "ceilometer_service"
Two events are also available to respond to:
- config_changed
- goneaway
A basic example showing the usage of this relation follows:
```
from charms.ceilometer_k8s.v0.ceilometer_service import (
CeilometerServiceRequires
)
class CeilometerServiceClientCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
# CeilometerService Requires
self.ceilometer_service = CeilometerServiceRequires(
self, "ceilometer_service",
)
self.framework.observe(
self.ceilometer_service.on.config_changed,
self._on_ceilometer_service_config_changed
)
self.framework.observe(
self.ceilometer_service.on.goneaway,
self._on_ceiometer_service_goneaway
)
def _on_ceilometer_service_config_changed(self, event):
'''React to the Ceilometer service config changed event.
This event happens when CeilometerService relation is added to the
model and relation data is changed.
'''
# Do something with the configuration provided by relation.
pass
def _on_ceilometer_service_goneaway(self, event):
'''React to the CeilometerService goneaway event.
This event happens when CeilometerService relation is removed.
'''
# CeilometerService Relation has goneaway.
pass
```
"""
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 = "fcbb94e7a18740729eaf9e2c3b90017f"
# 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 CeilometerConfigRequestEvent(RelationEvent):
"""CeilometerConfigRequest Event."""
pass
class CeilometerServiceProviderEvents(ObjectEvents):
"""Events class for `on`."""
config_request = EventSource(CeilometerConfigRequestEvent)
class CeilometerServiceProvides(Object):
"""CeilometerServiceProvides class."""
on = CeilometerServiceProviderEvents()
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_ceilometer_service_relation_changed,
)
def _on_ceilometer_service_relation_changed(
self, event: RelationChangedEvent
):
"""Handle CeilometerService relation changed."""
logging.debug("CeilometerService relation changed")
self.on.config_request.emit(event.relation)
def set_config(
self, relation: Optional[Relation], telemetry_secret: str
) -> None:
"""Set ceilometer configuration on the relation."""
if not self.charm.unit.is_leader():
logging.debug("Not a leader unit, skipping set config")
return
# If relation is not provided send config to all the related
# applications. This happens usually when config data is
# updated by provider and wants to send the data to all
# related applications
if relation is None:
logging.debug(
"Sending config to all related applications of relation"
f"{self.relation_name}"
)
for relation in self.framework.model.relations[self.relation_name]:
relation.data[self.charm.app][
"telemetry-secret"
] = telemetry_secret
else:
logging.debug(
f"Sending config on relation {relation.app.name} "
f"{relation.name}/{relation.id}"
)
relation.data[self.charm.app][
"telemetry-secret"
] = telemetry_secret
class CeilometerConfigChangedEvent(RelationEvent):
"""CeilometerConfigChanged Event."""
pass
class CeilometerServiceGoneAwayEvent(RelationEvent):
"""CeilometerServiceGoneAway Event."""
pass
class CeilometerServiceRequirerEvents(ObjectEvents):
"""Events class for `on`."""
config_changed = EventSource(CeilometerConfigChangedEvent)
goneaway = EventSource(CeilometerServiceGoneAwayEvent)
class CeilometerServiceRequires(Object):
"""CeilometerServiceRequires class."""
on = CeilometerServiceRequirerEvents()
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_ceilometer_service_relation_changed,
)
self.framework.observe(
self.charm.on[relation_name].relation_broken,
self._on_ceilometer_service_relation_broken,
)
def _on_ceilometer_service_relation_changed(
self, event: RelationChangedEvent
):
"""Handle CeilometerService relation changed."""
logging.debug("CeilometerService config data changed")
self.on.config_changed.emit(event.relation)
def _on_ceilometer_service_relation_broken(
self, event: RelationBrokenEvent
):
"""Handle CeilometerService relation changed."""
logging.debug("CeilometerService on_broken")
self.on.goneaway.emit(event.relation)
@property
def _ceilometer_service_rel(self) -> Optional[Relation]:
"""The ceilometer 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._ceilometer_service_rel:
data = self._ceilometer_service_rel.data[
self._ceilometer_service_rel.app
]
return data.get(key)
return None
@property
def telemetry_secret(self) -> Optional[str]:
"""Return the telemetry_secret."""
return self.get_remote_app_data("telemetry-secret")

View File

@ -17,6 +17,9 @@ requires:
certificates:
interface: tls-certificates
optional: true
ceilometer-service:
interface: ceilometer
optional: true
provides:
cos-agent:

View File

@ -34,6 +34,10 @@ import ops_sunbeam.charm as sunbeam_charm
import ops_sunbeam.guard as sunbeam_guard
import ops_sunbeam.ovn.relation_handlers as ovn_relation_handlers
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
from charms.ceilometer_k8s.v0.ceilometer_service import (
CeilometerConfigChangedEvent,
CeilometerServiceGoneAwayEvent,
)
from charms.grafana_agent.v0.cos_agent import COSAgentProvider
from ops.charm import ActionEvent
from ops.main import main
@ -56,6 +60,8 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
super().__init__(framework)
self._state.set_default(metadata_secret="")
self.enable_monitoring = self.check_relation_exists("cos-agent")
# Enable telemetry when ceilometer-service relation is joined
self.enable_telemetry = self.check_relation_exists("ceilometer-service")
self.framework.observe(
self.on.set_hypervisor_local_settings_action,
self._set_hypervisor_local_settings_action,
@ -104,6 +110,14 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
"ovsdb-cms" in self.mandatory_relations,
)
handlers.append(self.ovsdb_cms)
if self.can_add_handler("ceilometer-service", handlers):
self.ceilometer = sunbeam_rhandlers.CeilometerServiceRequiresHandler(
self,
"ceilometer-service",
self.handle_ceilometer_events,
"ceilometer-service" in self.mandatory_relations,
)
handlers.append(self.ceilometer)
handlers = super().get_relation_handlers(handlers)
return handlers
@ -238,10 +252,35 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
}
except AttributeError as e:
raise sunbeam_guard.WaitingExceptionError("Data missing: {}".format(e.name))
# Handle optional config contexts
try:
if contexts.ceilometer_service.telemetry_secret:
snap_data.update(
{
"telemetry.enable": self.enable_telemetry,
"telemetry.publisher-secret": contexts.ceilometer_service.telemetry_secret,
}
)
else:
snap_data.update({"telemetry.enable": self.enable_telemetry})
except AttributeError:
logger.debug("ceilometer_service relation not integrated")
snap_data.update({"telemetry.enable": self.enable_telemetry})
self.set_snap_data(snap_data)
self.ensure_services_running()
self._state.unit_bootstrapped = True
def handle_ceilometer_events(self, event: ops.framework.EventBase) -> None:
"""Handle ceilometer events."""
if isinstance(event, CeilometerConfigChangedEvent):
self.enable_telemetry = True
self.configure_charm(event)
elif isinstance(event, CeilometerServiceGoneAwayEvent):
self.enable_telemetry = False
self.configure_charm(event)
if __name__ == "__main__": # pragma: no cover
main(HypervisorOperatorCharm)

View File

@ -77,6 +77,61 @@ class TestCharm(test_utils.CharmTestCase):
},
)
def test_mandatory_relations(self):
"""Test all the charms relations."""
self.get_local_ip_by_default_route.return_value = "10.0.0.10"
hypervisor_snap_mock = mock.MagicMock()
hypervisor_snap_mock.present = False
self.snap.SnapState.Latest = "latest"
self.snap.SnapCache.return_value = {"openstack-hypervisor": hypervisor_snap_mock}
self.socket.getfqdn.return_value = "test.local"
self.initial_setup()
self.harness.set_leader()
hypervisor_snap_mock.ensure.assert_any_call("latest", channel="essex/stable")
test_utils.add_complete_amqp_relation(self.harness)
test_utils.add_complete_identity_credentials_relation(self.harness)
metadata = self.harness.charm.metadata_secret()
ovn_cacert = test_utils.TEST_CA + "\n" + "\n".join(test_utils.TEST_CHAIN)
ovn_cacert = base64.b64encode(ovn_cacert.encode()).decode()
private_key = base64.b64encode(
self.harness.charm.contexts().certificates.key.encode()
).decode()
certificate = base64.b64encode(test_utils.TEST_SERVER_CERT.encode()).decode()
expect_settings = {
"compute.cpu-mode": "host-model",
"compute.spice-proxy-address": "10.0.0.10",
"compute.virt-type": "kvm",
"credentials.ovn-metadata-proxy-shared-secret": metadata,
"identity.admin-role": None,
"identity.auth-url": "http://10.153.2.45:80/openstack-keystone",
"identity.password": "user-password",
"identity.project-domain-id": "pdomain-id",
"identity.project-domain-name": "pdomain_-ame",
"identity.project-name": "user-project",
"identity.region-name": "region12",
"identity.user-domain-id": "udomain-id",
"identity.user-domain-name": "udomain-name",
"identity.username": "username",
"logging.debug": False,
"monitoring.enable": False,
"network.dns-domain": "openstack.local",
"network.dns-servers": "8.8.8.8",
"network.enable-gateway": False,
"network.external-bridge": "br-ex",
"network.external-bridge-address": "10.20.20.1/24",
"network.ip-address": "10.0.0.10",
"network.ovn-cacert": ovn_cacert,
"network.ovn-cert": certificate,
"network.ovn-key": private_key,
"network.ovn-sb-connection": "ssl:10.20.21.10:6642",
"network.physnet-name": "physnet1",
"node.fqdn": "test.local",
"node.ip-address": "10.0.0.10",
"rabbitmq.url": "rabbit://hypervisor:rabbit.pass@10.0.0.13:5672/openstack",
"telemetry.enable": False,
}
hypervisor_snap_mock.set.assert_any_call(expect_settings, typed=True)
def test_all_relations(self):
"""Test all the charms relations."""
# Add cos-agent relation
@ -91,6 +146,11 @@ class TestCharm(test_utils.CharmTestCase):
},
)
# Add ceilometer-service relation
self.harness.add_relation(
"ceilometer-service", "ceilometer", app_data={"telemetry-secret": "FAKE_SECRET"}
)
self.get_local_ip_by_default_route.return_value = "10.0.0.10"
hypervisor_snap_mock = mock.MagicMock()
hypervisor_snap_mock.present = False
@ -125,6 +185,7 @@ class TestCharm(test_utils.CharmTestCase):
"identity.user-domain-name": "udomain-name",
"identity.username": "username",
"logging.debug": False,
"monitoring.enable": True,
"network.dns-domain": "openstack.local",
"network.dns-servers": "8.8.8.8",
"network.enable-gateway": False,
@ -139,6 +200,7 @@ class TestCharm(test_utils.CharmTestCase):
"node.fqdn": "test.local",
"node.ip-address": "10.0.0.10",
"rabbitmq.url": "rabbit://hypervisor:rabbit.pass@10.0.0.13:5672/openstack",
"monitoring.enable": True,
"telemetry.enable": True,
"telemetry.publisher-secret": "FAKE_SECRET",
}
hypervisor_snap_mock.set.assert_any_call(expect_settings, typed=True)