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:
parent
1ac1e494c9
commit
b3d4568882
@ -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")
|
@ -17,6 +17,9 @@ requires:
|
||||
certificates:
|
||||
interface: tls-certificates
|
||||
optional: true
|
||||
ceilometer-service:
|
||||
interface: ceilometer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
cos-agent:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user