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:
|
certificates:
|
||||||
interface: tls-certificates
|
interface: tls-certificates
|
||||||
optional: true
|
optional: true
|
||||||
|
ceilometer-service:
|
||||||
|
interface: ceilometer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
cos-agent:
|
cos-agent:
|
||||||
|
@ -34,6 +34,10 @@ import ops_sunbeam.charm as sunbeam_charm
|
|||||||
import ops_sunbeam.guard as sunbeam_guard
|
import ops_sunbeam.guard as sunbeam_guard
|
||||||
import ops_sunbeam.ovn.relation_handlers as ovn_relation_handlers
|
import ops_sunbeam.ovn.relation_handlers as ovn_relation_handlers
|
||||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
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 charms.grafana_agent.v0.cos_agent import COSAgentProvider
|
||||||
from ops.charm import ActionEvent
|
from ops.charm import ActionEvent
|
||||||
from ops.main import main
|
from ops.main import main
|
||||||
@ -56,6 +60,8 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
|
|||||||
super().__init__(framework)
|
super().__init__(framework)
|
||||||
self._state.set_default(metadata_secret="")
|
self._state.set_default(metadata_secret="")
|
||||||
self.enable_monitoring = self.check_relation_exists("cos-agent")
|
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.framework.observe(
|
||||||
self.on.set_hypervisor_local_settings_action,
|
self.on.set_hypervisor_local_settings_action,
|
||||||
self._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,
|
"ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
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)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
return handlers
|
return handlers
|
||||||
|
|
||||||
@ -238,10 +252,35 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
|
|||||||
}
|
}
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
raise sunbeam_guard.WaitingExceptionError("Data missing: {}".format(e.name))
|
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.set_snap_data(snap_data)
|
||||||
self.ensure_services_running()
|
self.ensure_services_running()
|
||||||
self._state.unit_bootstrapped = True
|
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
|
if __name__ == "__main__": # pragma: no cover
|
||||||
main(HypervisorOperatorCharm)
|
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):
|
def test_all_relations(self):
|
||||||
"""Test all the charms relations."""
|
"""Test all the charms relations."""
|
||||||
# Add cos-agent relation
|
# 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"
|
self.get_local_ip_by_default_route.return_value = "10.0.0.10"
|
||||||
hypervisor_snap_mock = mock.MagicMock()
|
hypervisor_snap_mock = mock.MagicMock()
|
||||||
hypervisor_snap_mock.present = False
|
hypervisor_snap_mock.present = False
|
||||||
@ -125,6 +185,7 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
"identity.user-domain-name": "udomain-name",
|
"identity.user-domain-name": "udomain-name",
|
||||||
"identity.username": "username",
|
"identity.username": "username",
|
||||||
"logging.debug": False,
|
"logging.debug": False,
|
||||||
|
"monitoring.enable": True,
|
||||||
"network.dns-domain": "openstack.local",
|
"network.dns-domain": "openstack.local",
|
||||||
"network.dns-servers": "8.8.8.8",
|
"network.dns-servers": "8.8.8.8",
|
||||||
"network.enable-gateway": False,
|
"network.enable-gateway": False,
|
||||||
@ -139,6 +200,7 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
"node.fqdn": "test.local",
|
"node.fqdn": "test.local",
|
||||||
"node.ip-address": "10.0.0.10",
|
"node.ip-address": "10.0.0.10",
|
||||||
"rabbitmq.url": "rabbit://hypervisor:rabbit.pass@10.0.0.13:5672/openstack",
|
"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)
|
hypervisor_snap_mock.set.assert_any_call(expect_settings, typed=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user