[ops-sunbeam] Ensure external connectivity for machine charms
Machine charms need external connectivity to access services hosted on a K8S substrate. Ensure rabbitmq / ovn relay are access remotely for machine charms. Closes-Bug: #2098974 Change-Id: Ifadb196dd6d60e33feab7dc0d835a7ea84444b9e Signed-off-by: Guillaume Boutry <guillaume.boutry@canonical.com>
This commit is contained in:
parent
cb27776b43
commit
4d4b4a41b0
@ -431,7 +431,8 @@ class NeutronOVNOperatorCharm(NeutronOperatorCharm):
|
|||||||
self,
|
self,
|
||||||
"ovsdb-cms",
|
"ovsdb-cms",
|
||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
external_connectivity=self.remote_external_access,
|
||||||
|
mandatory="ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
handlers = super().get_relation_handlers(handlers)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
|
@ -192,14 +192,6 @@ class OctaviaOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
) -> List[sunbeam_rhandlers.RelationHandler]:
|
) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||||
"""Relation handlers for the service."""
|
"""Relation handlers for the service."""
|
||||||
handlers = handlers or []
|
handlers = handlers or []
|
||||||
if self.can_add_handler("ovsdb-cms", handlers):
|
|
||||||
self.ovsdb_cms = ovn_rhandlers.OVSDBCMSRequiresHandler(
|
|
||||||
self,
|
|
||||||
"ovsdb-cms",
|
|
||||||
self.configure_charm,
|
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
|
||||||
)
|
|
||||||
handlers.append(self.ovsdb_cms)
|
|
||||||
if self.can_add_handler("identity-ops", handlers):
|
if self.can_add_handler("identity-ops", handlers):
|
||||||
self.id_ops = sunbeam_rhandlers.IdentityResourceRequiresHandler(
|
self.id_ops = sunbeam_rhandlers.IdentityResourceRequiresHandler(
|
||||||
self,
|
self,
|
||||||
@ -334,7 +326,8 @@ class OctaviaOVNOperatorCharm(OctaviaOperatorCharm):
|
|||||||
self,
|
self,
|
||||||
"ovsdb-cms",
|
"ovsdb-cms",
|
||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
external_connectivity=self.remote_external_access,
|
||||||
|
mandatory="ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
handlers = super().get_relation_handlers(handlers)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
|
@ -288,7 +288,8 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
|
|||||||
self,
|
self,
|
||||||
"ovsdb-cms",
|
"ovsdb-cms",
|
||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
external_connectivity=self.remote_external_access,
|
||||||
|
mandatory="ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
if self.can_add_handler("nova-service", handlers):
|
if self.can_add_handler("nova-service", handlers):
|
||||||
|
@ -61,12 +61,11 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
self.harness.update_config({"snap-channel": "essex/stable"})
|
self.harness.update_config({"snap-channel": "essex/stable"})
|
||||||
self.harness.begin_with_initial_hooks()
|
self.harness.begin_with_initial_hooks()
|
||||||
test_utils.add_complete_certificates_relation(self.harness)
|
test_utils.add_complete_certificates_relation(self.harness)
|
||||||
ovs_rel_id = self.harness.add_relation("ovsdb-cms", "ovn-relay")
|
self.harness.add_relation(
|
||||||
self.harness.add_relation_unit(ovs_rel_id, "ovn-relay/0")
|
"ovsdb-cms",
|
||||||
self.harness.update_relation_data(
|
"ovn-relay",
|
||||||
ovs_rel_id,
|
app_data={"loadbalancer-address": "10.15.24.37"},
|
||||||
"ovn-relay/0",
|
unit_data={
|
||||||
{
|
|
||||||
"bound-address": "10.1.176.143",
|
"bound-address": "10.1.176.143",
|
||||||
"bound-hostname": "ovn-relay-0.ovn-relay-endpoints.openstack.svc.cluster.local",
|
"bound-hostname": "ovn-relay-0.ovn-relay-endpoints.openstack.svc.cluster.local",
|
||||||
"egress-subnets": "10.20.21.10/32",
|
"egress-subnets": "10.20.21.10/32",
|
||||||
@ -75,6 +74,7 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
"private-address": "10.20.21.10",
|
"private-address": "10.20.21.10",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
ceph_rel_id = self.harness.add_relation("ceph-access", "cinder-ceph")
|
ceph_rel_id = self.harness.add_relation("ceph-access", "cinder-ceph")
|
||||||
self.harness.add_relation_unit(ceph_rel_id, "cinder-ceph/0")
|
self.harness.add_relation_unit(ceph_rel_id, "cinder-ceph/0")
|
||||||
|
|
||||||
@ -166,11 +166,11 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
"network.ovn-cacert": cacert_with_intermediates,
|
"network.ovn-cacert": cacert_with_intermediates,
|
||||||
"network.ovn-cert": certificate,
|
"network.ovn-cert": certificate,
|
||||||
"network.ovn-key": private_key,
|
"network.ovn-key": private_key,
|
||||||
"network.ovn-sb-connection": "ssl:10.20.21.10:6642",
|
"network.ovn-sb-connection": "ssl:10.15.24.37:6642",
|
||||||
"network.physnet-name": "physnet1",
|
"network.physnet-name": "physnet1",
|
||||||
"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@rabbithost1.local:5672/openstack",
|
||||||
"telemetry.enable": False,
|
"telemetry.enable": False,
|
||||||
"ca.bundle": None,
|
"ca.bundle": None,
|
||||||
"masakari.enable": False,
|
"masakari.enable": False,
|
||||||
@ -278,11 +278,11 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
"network.ovn-cacert": cacert_with_intermediates,
|
"network.ovn-cacert": cacert_with_intermediates,
|
||||||
"network.ovn-cert": certificate,
|
"network.ovn-cert": certificate,
|
||||||
"network.ovn-key": private_key,
|
"network.ovn-key": private_key,
|
||||||
"network.ovn-sb-connection": "ssl:10.20.21.10:6642",
|
"network.ovn-sb-connection": "ssl:10.15.24.37:6642",
|
||||||
"network.physnet-name": "physnet1",
|
"network.physnet-name": "physnet1",
|
||||||
"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@rabbithost1.local:5672/openstack",
|
||||||
"telemetry.enable": True,
|
"telemetry.enable": True,
|
||||||
"telemetry.publisher-secret": "FAKE_SECRET",
|
"telemetry.publisher-secret": "FAKE_SECRET",
|
||||||
"ca.bundle": None,
|
"ca.bundle": None,
|
||||||
|
@ -19,14 +19,17 @@ after you have pushed v3.
|
|||||||
Markdown is supported, following the CommonMark specification.
|
Markdown is supported, following the CommonMark specification.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import ops
|
||||||
from ops.framework import (
|
from ops.framework import (
|
||||||
StoredState,
|
|
||||||
EventBase,
|
EventBase,
|
||||||
ObjectEvents,
|
|
||||||
EventSource,
|
EventSource,
|
||||||
Object,
|
Object,
|
||||||
|
ObjectEvents,
|
||||||
|
StoredState,
|
||||||
)
|
)
|
||||||
|
|
||||||
# The unique Charmhub library identifier, never change it
|
# The unique Charmhub library identifier, never change it
|
||||||
@ -37,7 +40,11 @@ LIBAPI = 0
|
|||||||
|
|
||||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||||
# to 0 if you are raising the major API version
|
# to 0 if you are raising the major API version
|
||||||
LIBPATCH = 3
|
LIBPATCH = 4
|
||||||
|
|
||||||
|
|
||||||
|
LOADBALANCER_KEY = "loadbalancer-address"
|
||||||
|
EXTERNAL_KEY = "external-connectivity"
|
||||||
|
|
||||||
|
|
||||||
# TODO: add your code here! Happy coding!
|
# TODO: add your code here! Happy coding!
|
||||||
@ -75,10 +82,16 @@ class OVSDBCMSRequires(Object):
|
|||||||
on = OVSDBCMSServerEvents()
|
on = OVSDBCMSServerEvents()
|
||||||
_stored = StoredState()
|
_stored = StoredState()
|
||||||
|
|
||||||
def __init__(self, charm, relation_name: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm: ops.CharmBase,
|
||||||
|
relation_name: str,
|
||||||
|
external_connectivity: bool = False,
|
||||||
|
):
|
||||||
super().__init__(charm, relation_name)
|
super().__init__(charm, relation_name)
|
||||||
self.charm = charm
|
self.charm = charm
|
||||||
self.relation_name = relation_name
|
self.relation_name = relation_name
|
||||||
|
self.external_connectivity = external_connectivity
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.charm.on[relation_name].relation_joined,
|
self.charm.on[relation_name].relation_joined,
|
||||||
self._on_ovsdb_cms_relation_joined,
|
self._on_ovsdb_cms_relation_joined,
|
||||||
@ -95,6 +108,15 @@ class OVSDBCMSRequires(Object):
|
|||||||
self.charm.on[relation_name].relation_broken,
|
self.charm.on[relation_name].relation_broken,
|
||||||
self._on_ovsdb_cms_relation_broken,
|
self._on_ovsdb_cms_relation_broken,
|
||||||
)
|
)
|
||||||
|
self.request_access(external_connectivity)
|
||||||
|
|
||||||
|
def request_access(self, external_connectivity: bool) -> None:
|
||||||
|
"""Request access to the external connectivity."""
|
||||||
|
if self.model.unit.is_leader():
|
||||||
|
for rel in self.model.relations[self.relation_name]:
|
||||||
|
rel.data[self.model.app][EXTERNAL_KEY] = json.dumps(
|
||||||
|
external_connectivity
|
||||||
|
)
|
||||||
|
|
||||||
def _on_ovsdb_cms_relation_joined(self, event):
|
def _on_ovsdb_cms_relation_joined(self, event):
|
||||||
"""OVSDBCMS relation joined."""
|
"""OVSDBCMS relation joined."""
|
||||||
@ -107,7 +129,15 @@ class OVSDBCMSRequires(Object):
|
|||||||
def bound_addresses(self):
|
def bound_addresses(self):
|
||||||
return self.get_all_unit_values("bound-address")
|
return self.get_all_unit_values("bound-address")
|
||||||
|
|
||||||
def remote_ready(self):
|
def loadbalancer_address(self) -> str | None:
|
||||||
|
relation = self.model.get_relation(self.relation_name)
|
||||||
|
if relation:
|
||||||
|
return relation.data[relation.app].get(LOADBALANCER_KEY)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def remote_ready(self) -> bool:
|
||||||
|
if self.external_connectivity:
|
||||||
|
return self.loadbalancer_address() is not None
|
||||||
return all(self.bound_hostnames()) or all(self.bound_addresses())
|
return all(self.bound_hostnames()) or all(self.bound_addresses())
|
||||||
|
|
||||||
def _on_ovsdb_cms_relation_changed(self, event):
|
def _on_ovsdb_cms_relation_changed(self, event):
|
||||||
@ -131,7 +161,6 @@ class OVSDBCMSRequires(Object):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OVSDBCMSClientConnectedEvent(EventBase):
|
class OVSDBCMSClientConnectedEvent(EventBase):
|
||||||
"""OVSDBCMS connected Event."""
|
"""OVSDBCMS connected Event."""
|
||||||
|
|
||||||
@ -166,10 +195,16 @@ class OVSDBCMSProvides(Object):
|
|||||||
on = OVSDBCMSClientEvents()
|
on = OVSDBCMSClientEvents()
|
||||||
_stored = StoredState()
|
_stored = StoredState()
|
||||||
|
|
||||||
def __init__(self, charm, relation_name):
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm: ops.CharmBase,
|
||||||
|
relation_name: str,
|
||||||
|
loadbalancer_address: str | None = None,
|
||||||
|
):
|
||||||
super().__init__(charm, relation_name)
|
super().__init__(charm, relation_name)
|
||||||
self.charm = charm
|
self.charm = charm
|
||||||
self.relation_name = relation_name
|
self.relation_name = relation_name
|
||||||
|
self.loadbalancer_address = loadbalancer_address
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.charm.on[relation_name].relation_joined,
|
self.charm.on[relation_name].relation_joined,
|
||||||
self._on_ovsdb_cms_relation_joined,
|
self._on_ovsdb_cms_relation_joined,
|
||||||
@ -182,6 +217,17 @@ class OVSDBCMSProvides(Object):
|
|||||||
self.charm.on[relation_name].relation_broken,
|
self.charm.on[relation_name].relation_broken,
|
||||||
self._on_ovsdb_cms_relation_broken,
|
self._on_ovsdb_cms_relation_broken,
|
||||||
)
|
)
|
||||||
|
self.update_relation_data(loadbalancer_address)
|
||||||
|
|
||||||
|
def update_relation_data(
|
||||||
|
self, loadbalancer_address: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Update relation data."""
|
||||||
|
if loadbalancer_address and self.model.unit.is_leader():
|
||||||
|
for rel in self.model.relations[self.relation_name]:
|
||||||
|
rel.data[self.model.app][
|
||||||
|
LOADBALANCER_KEY
|
||||||
|
] = loadbalancer_address
|
||||||
|
|
||||||
def _on_ovsdb_cms_relation_joined(self, event):
|
def _on_ovsdb_cms_relation_joined(self, event):
|
||||||
"""Handle ovsdb-cms joined."""
|
"""Handle ovsdb-cms joined."""
|
||||||
|
@ -233,7 +233,7 @@ class OVNCentralOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
|||||||
self,
|
self,
|
||||||
"ovsdb-cms",
|
"ovsdb-cms",
|
||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
mandatory="ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
handlers = super().get_relation_handlers(handlers)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
|
@ -95,7 +95,6 @@ class OVNRelayOperatorCharm(ovn_charm.OSBaseOVNOperatorCharm):
|
|||||||
|
|
||||||
def __init__(self, framework):
|
def __init__(self, framework):
|
||||||
super().__init__(framework)
|
super().__init__(framework)
|
||||||
|
|
||||||
service_ports = [ServicePort(6642, name="southbound")]
|
service_ports = [ServicePort(6642, name="southbound")]
|
||||||
self.lb_handler = KubernetesLoadBalancerHandler(
|
self.lb_handler = KubernetesLoadBalancerHandler(
|
||||||
self,
|
self,
|
||||||
@ -115,11 +114,19 @@ class OVNRelayOperatorCharm(ovn_charm.OSBaseOVNOperatorCharm):
|
|||||||
"""Relation handlers for the service."""
|
"""Relation handlers for the service."""
|
||||||
handlers = handlers or []
|
handlers = handlers or []
|
||||||
self.ovsdb_cms = ovn_relation_handlers.OVSDBCMSRequiresHandler(
|
self.ovsdb_cms = ovn_relation_handlers.OVSDBCMSRequiresHandler(
|
||||||
self, "ovsdb-cms", self.configure_charm, True
|
self,
|
||||||
|
"ovsdb-cms",
|
||||||
|
self.configure_charm,
|
||||||
|
external_connectivity=self.remote_external_access,
|
||||||
|
mandatory=True,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
self.ovsdb_cms_relay = ovn_relation_handlers.OVSDBCMSProvidesHandler(
|
self.ovsdb_cms_relay = ovn_relation_handlers.OVSDBCMSProvidesHandler(
|
||||||
self, "ovsdb-cms-relay", self.configure_charm, False
|
self,
|
||||||
|
"ovsdb-cms-relay",
|
||||||
|
self.configure_charm,
|
||||||
|
loadbalancer_address=self.lb_handler.get_loadbalancer_ip(),
|
||||||
|
mandatory=False,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms_relay)
|
handlers.append(self.ovsdb_cms_relay)
|
||||||
handlers = super().get_relation_handlers(handlers)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
@ -129,11 +136,14 @@ class OVNRelayOperatorCharm(ovn_charm.OSBaseOVNOperatorCharm):
|
|||||||
event.set_results({"url": self.southbound_db_url})
|
event.set_results({"url": self.southbound_db_url})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ingress_address(self) -> Union[IPv4Address, IPv6Address]:
|
def ingress_address(self) -> Union[str, IPv4Address, IPv6Address]:
|
||||||
"""Network IP address for access to the OVN relay service."""
|
"""Network IP address for access to the OVN relay service."""
|
||||||
return self.model.get_binding(
|
return (
|
||||||
"ovsdb-cms-relay"
|
self.lb_handler.get_loadbalancer_ip()
|
||||||
).network.ingress_addresses[0]
|
or self.model.get_binding(
|
||||||
|
"ovsdb-cms-relay"
|
||||||
|
).network.ingress_addresses[0]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def southbound_db_url(self) -> str:
|
def southbound_db_url(self) -> str:
|
||||||
@ -170,6 +180,14 @@ class OVNRelayOperatorCharm(ovn_charm.OSBaseOVNOperatorCharm):
|
|||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def get_sans_ips(self) -> list[str]:
|
||||||
|
"""Return list of SANs for the certificate."""
|
||||||
|
sans_ips = super().get_sans_ips()
|
||||||
|
lb_address = self.lb_handler.get_loadbalancer_ip()
|
||||||
|
if lb_address and lb_address not in sans_ips:
|
||||||
|
sans_ips.append(lb_address)
|
||||||
|
return sans_ips
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: nocover
|
if __name__ == "__main__": # pragma: nocover
|
||||||
ops.main(OVNRelayOperatorCharm)
|
ops.main(OVNRelayOperatorCharm)
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
"""Tests for OVN relay."""
|
"""Tests for OVN relay."""
|
||||||
|
|
||||||
|
from unittest.mock import (
|
||||||
|
Mock,
|
||||||
|
)
|
||||||
|
|
||||||
import charm
|
import charm
|
||||||
import ops_sunbeam.test_utils as test_utils
|
import ops_sunbeam.test_utils as test_utils
|
||||||
|
|
||||||
@ -36,11 +40,16 @@ class _OVNRelayOperatorCharm(charm.OVNRelayOperatorCharm):
|
|||||||
class TestOVNRelayOperatorCharm(test_utils.CharmTestCase):
|
class TestOVNRelayOperatorCharm(test_utils.CharmTestCase):
|
||||||
"""Test OVN relay."""
|
"""Test OVN relay."""
|
||||||
|
|
||||||
PATCHES = []
|
PATCHES = [
|
||||||
|
"KubernetesLoadBalancerHandler",
|
||||||
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup OVN relay tests."""
|
"""Setup OVN relay tests."""
|
||||||
super().setUp(charm, self.PATCHES)
|
super().setUp(charm, self.PATCHES)
|
||||||
|
lb_handler = Mock()
|
||||||
|
lb_handler.get_loadbalancer_ip.return_value = "10.27.5.1"
|
||||||
|
self.KubernetesLoadBalancerHandler.return_value = lb_handler
|
||||||
self.harness = test_utils.get_harness(
|
self.harness = test_utils.get_harness(
|
||||||
_OVNRelayOperatorCharm, container_calls=self.container_calls
|
_OVNRelayOperatorCharm, container_calls=self.container_calls
|
||||||
)
|
)
|
||||||
@ -71,5 +80,5 @@ class TestOVNRelayOperatorCharm(test_utils.CharmTestCase):
|
|||||||
def test_southbound_db_url(self):
|
def test_southbound_db_url(self):
|
||||||
"""Return southbound db url."""
|
"""Return southbound db url."""
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"ssl:10.0.0.10:6642", self.harness.charm.southbound_db_url
|
"ssl:10.27.5.1:6642", self.harness.charm.southbound_db_url
|
||||||
)
|
)
|
||||||
|
229
libs/external/lib/charms/rabbitmq_k8s/v0/rabbitmq.py
vendored
229
libs/external/lib/charms/rabbitmq_k8s/v0/rabbitmq.py
vendored
@ -11,6 +11,7 @@ relation name:
|
|||||||
Also provide two additional parameters to the charm object:
|
Also provide two additional parameters to the charm object:
|
||||||
- username
|
- username
|
||||||
- vhost
|
- vhost
|
||||||
|
- external_connectivity: Optional, default False
|
||||||
|
|
||||||
Two events are also available to respond to:
|
Two events are also available to respond to:
|
||||||
- connected
|
- connected
|
||||||
@ -66,6 +67,12 @@ class RabbitMQClientCharm(CharmBase):
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import ops
|
||||||
|
|
||||||
# The unique Charmhub library identifier, never change it
|
# The unique Charmhub library identifier, never change it
|
||||||
LIBID = "45622352791142fd9cf87232e3bd6f2a"
|
LIBID = "45622352791142fd9cf87232e3bd6f2a"
|
||||||
|
|
||||||
@ -74,64 +81,57 @@ LIBAPI = 0
|
|||||||
|
|
||||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||||
# to 0 if you are raising the major API version
|
# to 0 if you are raising the major API version
|
||||||
LIBPATCH = 1
|
LIBPATCH = 3
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from ops.framework import (
|
|
||||||
StoredState,
|
|
||||||
EventBase,
|
|
||||||
ObjectEvents,
|
|
||||||
EventSource,
|
|
||||||
Object,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ops.model import Relation
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQConnectedEvent(EventBase):
|
class RabbitMQConnectedEvent(ops.EventBase):
|
||||||
"""RabbitMQ connected Event."""
|
"""RabbitMQ connected Event."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQReadyEvent(EventBase):
|
class RabbitMQReadyEvent(ops.EventBase):
|
||||||
"""RabbitMQ ready for use Event."""
|
"""RabbitMQ ready for use Event."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQGoneAwayEvent(EventBase):
|
class RabbitMQGoneAwayEvent(ops.EventBase):
|
||||||
"""RabbitMQ relation has gone-away Event"""
|
"""RabbitMQ relation has gone-away Event."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQServerEvents(ObjectEvents):
|
class RabbitMQServerEvents(ops.ObjectEvents):
|
||||||
"""Events class for `on`"""
|
"""Events class for `on`."""
|
||||||
|
|
||||||
connected = EventSource(RabbitMQConnectedEvent)
|
connected = ops.EventSource(RabbitMQConnectedEvent)
|
||||||
ready = EventSource(RabbitMQReadyEvent)
|
ready = ops.EventSource(RabbitMQReadyEvent)
|
||||||
goneaway = EventSource(RabbitMQGoneAwayEvent)
|
goneaway = ops.EventSource(RabbitMQGoneAwayEvent)
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQRequires(Object):
|
class RabbitMQRequires(ops.Object):
|
||||||
"""
|
"""RabbitMQRequires class."""
|
||||||
RabbitMQRequires class
|
|
||||||
"""
|
|
||||||
|
|
||||||
on = RabbitMQServerEvents()
|
on = RabbitMQServerEvents() # type: ignore
|
||||||
|
|
||||||
def __init__(self, charm, relation_name: str, username: str, vhost: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm,
|
||||||
|
relation_name: str,
|
||||||
|
username: str,
|
||||||
|
vhost: str,
|
||||||
|
external_connectivity: bool = False,
|
||||||
|
):
|
||||||
super().__init__(charm, relation_name)
|
super().__init__(charm, relation_name)
|
||||||
self.charm = charm
|
self.charm = charm
|
||||||
self.relation_name = relation_name
|
self.relation_name = relation_name
|
||||||
self.username = username
|
self.username = username
|
||||||
self.vhost = vhost
|
self.vhost = vhost
|
||||||
|
self.external_connectivity = external_connectivity
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.charm.on[relation_name].relation_joined,
|
self.charm.on[relation_name].relation_joined,
|
||||||
self._on_amqp_relation_joined,
|
self._on_amqp_relation_joined,
|
||||||
@ -149,91 +149,118 @@ class RabbitMQRequires(Object):
|
|||||||
self._on_amqp_relation_broken,
|
self._on_amqp_relation_broken,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_amqp_relation_joined(self, event):
|
def _on_amqp_relation_joined(self, event: ops.RelationJoinedEvent):
|
||||||
"""RabbitMQ relation joined."""
|
|
||||||
logging.debug("RabbitMQRabbitMQRequires on_joined")
|
logging.debug("RabbitMQRabbitMQRequires on_joined")
|
||||||
self.on.connected.emit()
|
self.on.connected.emit()
|
||||||
self.request_access(self.username, self.vhost)
|
self.request_access(
|
||||||
|
self.username, self.vhost, self.external_connectivity
|
||||||
|
)
|
||||||
|
|
||||||
def _on_amqp_relation_changed(self, event):
|
def _on_amqp_relation_changed(
|
||||||
"""RabbitMQ relation changed."""
|
self, event: ops.RelationChangedEvent | ops.RelationDepartedEvent
|
||||||
|
):
|
||||||
logging.debug("RabbitMQRabbitMQRequires on_changed/departed")
|
logging.debug("RabbitMQRabbitMQRequires on_changed/departed")
|
||||||
if self.password:
|
if self.password:
|
||||||
self.on.ready.emit()
|
self.on.ready.emit()
|
||||||
|
|
||||||
def _on_amqp_relation_broken(self, event):
|
def _on_amqp_relation_broken(self, event: ops.RelationBrokenEvent):
|
||||||
"""RabbitMQ relation broken."""
|
|
||||||
logging.debug("RabbitMQRabbitMQRequires on_broken")
|
logging.debug("RabbitMQRabbitMQRequires on_broken")
|
||||||
self.on.goneaway.emit()
|
self.on.goneaway.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _amqp_rel(self) -> Relation:
|
def _amqp_rel(self) -> ops.Relation | None:
|
||||||
"""The RabbitMQ relation."""
|
"""The RabbitMQ relation."""
|
||||||
return self.framework.model.get_relation(self.relation_name)
|
return self.framework.model.get_relation(self.relation_name)
|
||||||
|
|
||||||
|
def _get(self, key: str) -> str | None:
|
||||||
|
"""Return property from the RabbitMQ relation."""
|
||||||
|
rel = self._amqp_rel
|
||||||
|
if rel and rel.active:
|
||||||
|
return rel.data[rel.app].get(key)
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self) -> str:
|
def password(self) -> str | None:
|
||||||
"""Return the RabbitMQ password from the server side of the relation."""
|
"""Return the RabbitMQ password from the server side of the relation."""
|
||||||
return self._amqp_rel.data[self._amqp_rel.app].get("password")
|
return self._get("password")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self) -> str:
|
def hostname(self) -> str | None:
|
||||||
"""Return the hostname from the RabbitMQ relation"""
|
"""Return the hostname from the RabbitMQ relation."""
|
||||||
return self._amqp_rel.data[self._amqp_rel.app].get("hostname")
|
return self._get("hostname")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_port(self) -> str:
|
def ssl_port(self) -> str | None:
|
||||||
"""Return the SSL port from the RabbitMQ relation"""
|
"""Return the SSL port from the RabbitMQ relation."""
|
||||||
return self._amqp_rel.data[self._amqp_rel.app].get("ssl_port")
|
return self._get("ssl_port")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssl_ca(self) -> str:
|
def ssl_ca(self) -> str | None:
|
||||||
"""Return the SSL port from the RabbitMQ relation"""
|
"""Return the SSL port from the RabbitMQ relation."""
|
||||||
return self._amqp_rel.data[self._amqp_rel.app].get("ssl_ca")
|
return self._get("ssl_ca")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hostnames(self) -> List[str]:
|
def hostnames(self) -> list[str]:
|
||||||
"""Return a list of remote RMQ hosts from the RabbitMQ relation"""
|
"""Return a list of remote RMQ hosts from the RabbitMQ relation."""
|
||||||
_hosts = []
|
_hosts: list[str] = []
|
||||||
for unit in self._amqp_rel.units:
|
rel = self._amqp_rel
|
||||||
_hosts.append(self._amqp_rel.data[unit].get("ingress-address"))
|
if not rel:
|
||||||
|
return _hosts
|
||||||
|
for unit in rel.units:
|
||||||
|
if ingress := rel.data[unit].get("ingress-address"):
|
||||||
|
_hosts.append(ingress)
|
||||||
return _hosts
|
return _hosts
|
||||||
|
|
||||||
def request_access(self, username: str, vhost: str) -> None:
|
def request_access(
|
||||||
|
self, username: str, vhost: str, external_connectivity: bool
|
||||||
|
) -> None:
|
||||||
"""Request access to the RabbitMQ server."""
|
"""Request access to the RabbitMQ server."""
|
||||||
if self.model.unit.is_leader():
|
if (rel := self._amqp_rel) and self.model.unit.is_leader():
|
||||||
logging.debug("Requesting RabbitMQ user and vhost")
|
logging.debug("Requesting RabbitMQ user and vhost")
|
||||||
self._amqp_rel.data[self.charm.app]["username"] = username
|
rel.data[self.charm.app]["username"] = username
|
||||||
self._amqp_rel.data[self.charm.app]["vhost"] = vhost
|
rel.data[self.charm.app]["vhost"] = vhost
|
||||||
|
rel.data[self.charm.app]["external_connectivity"] = json.dumps(
|
||||||
|
external_connectivity
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HasRabbitMQClientsEvent(EventBase):
|
class HasRabbitMQClientsEvent(ops.RelationEvent):
|
||||||
"""Has RabbitMQClients Event."""
|
"""Has RabbitMQClients Event."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReadyRabbitMQClientsEvent(EventBase):
|
class ReadyRabbitMQClientsEvent(ops.RelationEvent):
|
||||||
"""RabbitMQClients Ready Event."""
|
"""RabbitMQClients Ready Event."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQClientEvents(ObjectEvents):
|
class GoneAwayRabbitMQClientsEvent(ops.RelationEvent):
|
||||||
"""Events class for `on`"""
|
"""RabbitMQClients GoneAway Event."""
|
||||||
|
|
||||||
has_amqp_clients = EventSource(HasRabbitMQClientsEvent)
|
pass
|
||||||
ready_amqp_clients = EventSource(ReadyRabbitMQClientsEvent)
|
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQProvides(Object):
|
class RabbitMQClientEvents(ops.ObjectEvents):
|
||||||
"""
|
"""Events class for `on`."""
|
||||||
RabbitMQProvides class
|
|
||||||
"""
|
|
||||||
|
|
||||||
on = RabbitMQClientEvents()
|
has_amqp_clients = ops.EventSource(HasRabbitMQClientsEvent)
|
||||||
|
ready_amqp_clients = ops.EventSource(ReadyRabbitMQClientsEvent)
|
||||||
|
gone_away_amqp_clients = ops.EventSource(GoneAwayRabbitMQClientsEvent)
|
||||||
|
|
||||||
def __init__(self, charm, relation_name, callback):
|
|
||||||
|
class RabbitMQProvides(ops.Object):
|
||||||
|
"""RabbitMQProvides class."""
|
||||||
|
|
||||||
|
on = RabbitMQClientEvents() # type: ignore
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm: ops.CharmBase,
|
||||||
|
relation_name: str,
|
||||||
|
callback: typing.Callable,
|
||||||
|
):
|
||||||
super().__init__(charm, relation_name)
|
super().__init__(charm, relation_name)
|
||||||
self.charm = charm
|
self.charm = charm
|
||||||
self.relation_name = relation_name
|
self.relation_name = relation_name
|
||||||
@ -251,36 +278,60 @@ class RabbitMQProvides(Object):
|
|||||||
self._on_amqp_relation_broken,
|
self._on_amqp_relation_broken,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_amqp_relation_joined(self, event):
|
def _on_amqp_relation_joined(self, event: ops.RelationJoinedEvent):
|
||||||
"""Handle RabbitMQ joined."""
|
"""Handle RabbitMQ joined."""
|
||||||
logging.debug("RabbitMQRabbitMQProvides on_joined data={}"
|
logging.debug(
|
||||||
.format(event.relation.data[event.relation.app]))
|
"RabbitMQRabbitMQProvides on_joined data={}".format(
|
||||||
self.on.has_amqp_clients.emit()
|
event.relation.data[event.relation.app]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.on.has_amqp_clients.emit(event.relation)
|
||||||
|
|
||||||
def _on_amqp_relation_changed(self, event):
|
def _on_amqp_relation_changed(self, event: ops.RelationChangedEvent):
|
||||||
"""Handle RabbitMQ changed."""
|
"""Handle RabbitMQ changed."""
|
||||||
logging.debug("RabbitMQRabbitMQProvides on_changed data={}"
|
relation = event.relation
|
||||||
.format(event.relation.data[event.relation.app]))
|
logging.debug(
|
||||||
|
"RabbitMQRabbitMQProvides on_changed data={}".format(
|
||||||
|
relation.data[relation.app]
|
||||||
|
)
|
||||||
|
)
|
||||||
# Validate data on the relation
|
# Validate data on the relation
|
||||||
if self.username(event) and self.vhost(event):
|
if self.username(relation) and self.vhost(relation):
|
||||||
self.on.ready_amqp_clients.emit()
|
self.on.ready_amqp_clients.emit(relation)
|
||||||
if self.charm.unit.is_leader():
|
if self.charm.unit.is_leader():
|
||||||
self.callback(event, self.username(event), self.vhost(event))
|
self.callback(
|
||||||
|
event,
|
||||||
|
self.username(relation),
|
||||||
|
self.vhost(relation),
|
||||||
|
self.external_connectivity(relation),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning("Received RabbitMQ changed event without the "
|
logging.warning(
|
||||||
"expected keys ('username', 'vhost') in the "
|
"Received RabbitMQ changed event without the "
|
||||||
"application data bag. Incompatible charm in "
|
"expected keys ('username', 'vhost') in the "
|
||||||
"other end of relation?")
|
"application data bag. Incompatible charm in "
|
||||||
|
"other end of relation?"
|
||||||
|
)
|
||||||
|
|
||||||
def _on_amqp_relation_broken(self, event):
|
def _on_amqp_relation_broken(self, event: ops.RelationBrokenEvent):
|
||||||
"""Handle RabbitMQ broken."""
|
"""Handle RabbitMQ broken."""
|
||||||
logging.debug("RabbitMQRabbitMQProvides on_departed")
|
logging.debug("RabbitMQRabbitMQProvides on_departed")
|
||||||
# TODO clear data on the relation
|
self.on.gone_away_amqp_clients.emit(event.relation)
|
||||||
|
|
||||||
def username(self, event):
|
def _get(self, relation: ops.Relation, key: str) -> str | None:
|
||||||
|
"""Return property from the RabbitMQ relation."""
|
||||||
|
return relation.data[relation.app].get(key)
|
||||||
|
|
||||||
|
def username(self, relation: ops.Relation) -> str | None:
|
||||||
"""Return the RabbitMQ username from the client side of the relation."""
|
"""Return the RabbitMQ username from the client side of the relation."""
|
||||||
return event.relation.data[event.relation.app].get("username")
|
return self._get(relation, "username")
|
||||||
|
|
||||||
def vhost(self, event):
|
def vhost(self, relation: ops.Relation) -> str | None:
|
||||||
"""Return the RabbitMQ vhost from the client side of the relation."""
|
"""Return the RabbitMQ vhost from the client side of the relation."""
|
||||||
return event.relation.data[event.relation.app].get("vhost")
|
return self._get(relation, "vhost")
|
||||||
|
|
||||||
|
def external_connectivity(self, relation: ops.Relation) -> bool:
|
||||||
|
"""Return the RabbitMQ external_connectivity from the client side of the relation."""
|
||||||
|
return json.loads(
|
||||||
|
self._get(relation, "external_connectivity") or "false"
|
||||||
|
)
|
||||||
|
@ -169,6 +169,7 @@ class OSBaseOperatorCharm(
|
|||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
str(self.config.get("rabbit-user") or self.service_name),
|
str(self.config.get("rabbit-user") or self.service_name),
|
||||||
str(self.config.get("rabbit-vhost") or "openstack"),
|
str(self.config.get("rabbit-vhost") or "openstack"),
|
||||||
|
self.remote_external_access,
|
||||||
"amqp" in self.mandatory_relations,
|
"amqp" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.amqp)
|
handlers.append(self.amqp)
|
||||||
@ -225,6 +226,17 @@ class OSBaseOperatorCharm(
|
|||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remote_external_access(self) -> bool:
|
||||||
|
"""Whether this charm needs external access for remote service.
|
||||||
|
|
||||||
|
If the service needs special handling for remote access, this function
|
||||||
|
should be overridden to return True.
|
||||||
|
|
||||||
|
Example, remote service needs to expose a LoadBalancer service.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def get_tracing_endpoint(self) -> str | None:
|
def get_tracing_endpoint(self) -> str | None:
|
||||||
"""Get the tracing endpoint for the service."""
|
"""Get the tracing endpoint for the service."""
|
||||||
if hasattr(self, "tracing"):
|
if hasattr(self, "tracing"):
|
||||||
@ -654,6 +666,14 @@ class OSBaseOperatorCharmK8S(OSBaseOperatorCharm):
|
|||||||
if h.container_name in container_names
|
if h.container_name in container_names
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remote_external_access(self) -> bool:
|
||||||
|
"""Whether this charm needs external access for remote service.
|
||||||
|
|
||||||
|
Most often, k8s services don't need a special access to communicate.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
def configure_containers(self):
|
def configure_containers(self):
|
||||||
"""Configure containers."""
|
"""Configure containers."""
|
||||||
for ph in self.pebble_handlers:
|
for ph in self.pebble_handlers:
|
||||||
|
@ -14,12 +14,16 @@
|
|||||||
|
|
||||||
"""Handles management of kubernetes resources."""
|
"""Handles management of kubernetes resources."""
|
||||||
|
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import ops_sunbeam.tracing as sunbeam_tracing
|
import ops_sunbeam.tracing as sunbeam_tracing
|
||||||
from lightkube.core.client import (
|
from lightkube.core.client import (
|
||||||
Client,
|
Client,
|
||||||
)
|
)
|
||||||
|
from lightkube.core.exceptions import (
|
||||||
|
ApiError,
|
||||||
|
)
|
||||||
from lightkube.models.core_v1 import (
|
from lightkube.models.core_v1 import (
|
||||||
ServicePort,
|
ServicePort,
|
||||||
ServiceSpec,
|
ServiceSpec,
|
||||||
@ -143,3 +147,30 @@ class KubernetesLoadBalancerHandler(Object):
|
|||||||
)
|
)
|
||||||
klm = self._get_lb_resource_manager()
|
klm = self._get_lb_resource_manager()
|
||||||
klm.delete()
|
klm.delete()
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def get_loadbalancer_ip(self) -> str | None:
|
||||||
|
"""Helper to get loadbalancer IP.
|
||||||
|
|
||||||
|
Result is cached for the whole duration of a hook.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
svc = self.lightkube_client.get(
|
||||||
|
Service, name=self._lb_name, namespace=self.model.name
|
||||||
|
)
|
||||||
|
except ApiError as e:
|
||||||
|
logger.error(f"Failed to fetch LoadBalancer {self._lb_name}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not (status := getattr(svc, "status", None)):
|
||||||
|
return None
|
||||||
|
if not (load_balancer_status := getattr(status, "loadBalancer", None)):
|
||||||
|
return None
|
||||||
|
if not (
|
||||||
|
ingress_addresses := getattr(load_balancer_status, "ingress", None)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
if not (ingress_address := ingress_addresses[0]):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return ingress_address.ip
|
||||||
|
@ -32,7 +32,8 @@ class OSBaseOVNOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
|||||||
self,
|
self,
|
||||||
"ovsdb-cms",
|
"ovsdb-cms",
|
||||||
self.configure_charm,
|
self.configure_charm,
|
||||||
"ovsdb-cms" in self.mandatory_relations,
|
external_connectivity=self.remote_external_access,
|
||||||
|
mandatory="ovsdb-cms" in self.mandatory_relations,
|
||||||
)
|
)
|
||||||
handlers.append(self.ovsdb_cms)
|
handlers.append(self.ovsdb_cms)
|
||||||
handlers = super().get_relation_handlers(handlers)
|
handlers = super().get_relation_handlers(handlers)
|
||||||
|
@ -492,6 +492,18 @@ class OVSDBCMSProvidesHandler(
|
|||||||
|
|
||||||
interface: "ovsdb.OVSDBCMSProvides"
|
interface: "ovsdb.OVSDBCMSProvides"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm: "OSBaseOperatorCharm",
|
||||||
|
relation_name: str,
|
||||||
|
callback_f: Callable,
|
||||||
|
loadbalancer_address: str | None = None,
|
||||||
|
mandatory: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Run constructor."""
|
||||||
|
super().__init__(charm, relation_name, callback_f, mandatory)
|
||||||
|
self.loadbalancer_address = loadbalancer_address
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
"""Post init hook."""
|
"""Post init hook."""
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@ -507,6 +519,7 @@ class OVSDBCMSProvidesHandler(
|
|||||||
ovsdb_svc = sunbeam_tracing.trace_type(ovsdb.OVSDBCMSProvides)(
|
ovsdb_svc = sunbeam_tracing.trace_type(ovsdb.OVSDBCMSProvides)(
|
||||||
self.charm,
|
self.charm,
|
||||||
self.relation_name,
|
self.relation_name,
|
||||||
|
self.loadbalancer_address,
|
||||||
)
|
)
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
ovsdb_svc.on.ready, self._on_ovsdb_service_ready
|
ovsdb_svc.on.ready, self._on_ovsdb_service_ready
|
||||||
@ -546,10 +559,12 @@ class OVSDBCMSRequiresHandler(
|
|||||||
charm: "OSBaseOperatorCharm",
|
charm: "OSBaseOperatorCharm",
|
||||||
relation_name: str,
|
relation_name: str,
|
||||||
callback_f: Callable,
|
callback_f: Callable,
|
||||||
|
external_connectivity: bool = False,
|
||||||
mandatory: bool = False,
|
mandatory: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run constructor."""
|
"""Run constructor."""
|
||||||
super().__init__(charm, relation_name, callback_f, mandatory)
|
super().__init__(charm, relation_name, callback_f, mandatory)
|
||||||
|
self.external_connectivity = external_connectivity
|
||||||
|
|
||||||
def setup_event_handler(self) -> ops.framework.Object:
|
def setup_event_handler(self) -> ops.framework.Object:
|
||||||
"""Configure event handlers for an Identity service relation."""
|
"""Configure event handlers for an Identity service relation."""
|
||||||
@ -561,6 +576,7 @@ class OVSDBCMSRequiresHandler(
|
|||||||
ovsdb_svc = sunbeam_tracing.trace_type(ovsdb.OVSDBCMSRequires)(
|
ovsdb_svc = sunbeam_tracing.trace_type(ovsdb.OVSDBCMSRequires)(
|
||||||
self.charm,
|
self.charm,
|
||||||
self.relation_name,
|
self.relation_name,
|
||||||
|
external_connectivity=self.external_connectivity,
|
||||||
)
|
)
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
ovsdb_svc.on.ready, self._on_ovsdb_service_ready
|
ovsdb_svc.on.ready, self._on_ovsdb_service_ready
|
||||||
@ -598,8 +614,6 @@ class OVSDBCMSRequiresHandler(
|
|||||||
"hostnames": self.interface.bound_hostnames(),
|
"hostnames": self.interface.bound_hostnames(),
|
||||||
"local_address": self.cluster_local_addr,
|
"local_address": self.cluster_local_addr,
|
||||||
"addresses": self.interface.bound_addresses(),
|
"addresses": self.interface.bound_addresses(),
|
||||||
"db_ingress_sb_connection_strs": self.db_ingress_sb_connection_strs,
|
|
||||||
"db_ingress_nb_connection_strs": self.db_ingress_nb_connection_strs,
|
|
||||||
"db_sb_connection_strs": ",".join(self.db_sb_connection_strs),
|
"db_sb_connection_strs": ",".join(self.db_sb_connection_strs),
|
||||||
"db_nb_connection_strs": ",".join(self.db_nb_connection_strs),
|
"db_nb_connection_strs": ",".join(self.db_nb_connection_strs),
|
||||||
"db_sb_connection_hostname_strs": ",".join(
|
"db_sb_connection_hostname_strs": ",".join(
|
||||||
@ -610,5 +624,19 @@ class OVSDBCMSRequiresHandler(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if lb_address := self.interface.loadbalancer_address():
|
||||||
|
ctxt["db_ingress_sb_connection_strs"] = self.db_connection_strs(
|
||||||
|
[lb_address], self.db_sb_port
|
||||||
|
)
|
||||||
|
ctxt["db_ingress_nb_connection_strs"] = self.db_connection_strs(
|
||||||
|
[lb_address], self.db_nb_port
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ctxt["db_ingress_sb_connection_strs"] = (
|
||||||
|
self.db_ingress_sb_connection_strs
|
||||||
|
)
|
||||||
|
ctxt["db_ingress_nb_connection_strs"] = (
|
||||||
|
self.db_ingress_nb_connection_strs
|
||||||
|
)
|
||||||
|
|
||||||
return ctxt
|
return ctxt
|
||||||
|
@ -448,12 +448,14 @@ class RabbitMQHandler(RelationHandler):
|
|||||||
callback_f: Callable,
|
callback_f: Callable,
|
||||||
username: str,
|
username: str,
|
||||||
vhost: str,
|
vhost: str,
|
||||||
|
external_connectivity: bool,
|
||||||
mandatory: bool = False,
|
mandatory: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run constructor."""
|
"""Run constructor."""
|
||||||
super().__init__(charm, relation_name, callback_f, mandatory)
|
super().__init__(charm, relation_name, callback_f, mandatory)
|
||||||
self.username = username
|
self.username = username
|
||||||
self.vhost = vhost
|
self.vhost = vhost
|
||||||
|
self.external_connectivity = external_connectivity
|
||||||
|
|
||||||
def setup_event_handler(self) -> ops.framework.Object:
|
def setup_event_handler(self) -> ops.framework.Object:
|
||||||
"""Configure event handlers for an AMQP relation."""
|
"""Configure event handlers for an AMQP relation."""
|
||||||
@ -463,12 +465,24 @@ class RabbitMQHandler(RelationHandler):
|
|||||||
import charms.rabbitmq_k8s.v0.rabbitmq as sunbeam_rabbitmq
|
import charms.rabbitmq_k8s.v0.rabbitmq as sunbeam_rabbitmq
|
||||||
|
|
||||||
amqp = sunbeam_tracing.trace_type(sunbeam_rabbitmq.RabbitMQRequires)(
|
amqp = sunbeam_tracing.trace_type(sunbeam_rabbitmq.RabbitMQRequires)(
|
||||||
self.charm, self.relation_name, self.username, self.vhost
|
self.charm,
|
||||||
|
self.relation_name,
|
||||||
|
self.username,
|
||||||
|
self.vhost,
|
||||||
|
self.external_connectivity,
|
||||||
)
|
)
|
||||||
self.framework.observe(amqp.on.ready, self._on_amqp_ready)
|
self.framework.observe(amqp.on.ready, self._on_amqp_ready)
|
||||||
self.framework.observe(amqp.on.goneaway, self._on_amqp_goneaway)
|
self.framework.observe(amqp.on.goneaway, self._on_amqp_goneaway)
|
||||||
return amqp
|
return amqp
|
||||||
|
|
||||||
|
def update_relation_data(self):
|
||||||
|
"""Update relation outside of relation context."""
|
||||||
|
self.interface.request_access(
|
||||||
|
self.username,
|
||||||
|
self.vhost,
|
||||||
|
self.external_connectivity,
|
||||||
|
)
|
||||||
|
|
||||||
def _on_amqp_ready(self, event: ops.framework.EventBase) -> None:
|
def _on_amqp_ready(self, event: ops.framework.EventBase) -> None:
|
||||||
"""Handle AMQP change events."""
|
"""Handle AMQP change events."""
|
||||||
# Ready is only emitted when the interface considers
|
# Ready is only emitted when the interface considers
|
||||||
@ -488,7 +502,7 @@ class RabbitMQHandler(RelationHandler):
|
|||||||
"""Whether handler is ready for use."""
|
"""Whether handler is ready for use."""
|
||||||
try:
|
try:
|
||||||
return bool(self.interface.password) and bool(
|
return bool(self.interface.password) and bool(
|
||||||
self.interface.hostnames
|
self.interface.hostname
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return False
|
return False
|
||||||
@ -496,29 +510,22 @@ class RabbitMQHandler(RelationHandler):
|
|||||||
def context(self) -> dict:
|
def context(self) -> dict:
|
||||||
"""Context containing AMQP connection data."""
|
"""Context containing AMQP connection data."""
|
||||||
try:
|
try:
|
||||||
hosts = self.interface.hostnames
|
host = self.interface.hostname
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return {}
|
return {}
|
||||||
if not hosts:
|
if not host:
|
||||||
return {}
|
return {}
|
||||||
ctxt = super().context()
|
ctxt = super().context()
|
||||||
ctxt["hostnames"] = list(set(ctxt["hostnames"]))
|
ctxt["hostname"] = host
|
||||||
ctxt["hosts"] = ",".join(ctxt["hostnames"])
|
|
||||||
ctxt["port"] = ctxt.get("ssl_port") or self.DEFAULT_PORT
|
ctxt["port"] = ctxt.get("ssl_port") or self.DEFAULT_PORT
|
||||||
transport_url_hosts = ",".join(
|
transport_url_host = "{}:{}@{}:{}".format(
|
||||||
[
|
self.username,
|
||||||
"{}:{}@{}:{}".format(
|
ctxt["password"],
|
||||||
self.username,
|
host, # TODO deal with IPv6
|
||||||
ctxt["password"],
|
ctxt["port"],
|
||||||
host_, # TODO deal with IPv6
|
|
||||||
ctxt["port"],
|
|
||||||
)
|
|
||||||
for host_ in ctxt["hostnames"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
transport_url = "rabbit://{}/{}".format(
|
|
||||||
transport_url_hosts, self.vhost
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
transport_url = "rabbit://{}/{}".format(transport_url_host, self.vhost)
|
||||||
ctxt["transport_url"] = transport_url
|
ctxt["transport_url"] = transport_url
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ class TestOSBaseOperatorAPICharm(_TestOSBaseOperatorAPICharm):
|
|||||||
"/bin/wsgi_admin",
|
"/bin/wsgi_admin",
|
||||||
"hardpassword",
|
"hardpassword",
|
||||||
"True",
|
"True",
|
||||||
"rabbit://my-service:rabbit.pass@10.0.0.13:5672/openstack",
|
"rabbit://my-service:rabbit.pass@rabbithost1.local:5672/openstack",
|
||||||
"rabbithost1.local",
|
"rabbithost1.local",
|
||||||
"svcpass1",
|
"svcpass1",
|
||||||
"bar",
|
"bar",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user