Support external dns for cloud guests
Add interface designate to share the dns endpoint. Update designate-k8s to implement provides side of the interface and add endpoint data to the relation app databag. Update neutron-k8s to implement requires side of the interface. Add new options reverse-dns-lookup, ipv4-ptr-zone-prefix-size, ipv6-ptr-zone-prefix-size. Update neutron conf templates to add external dns related configuration. Change-Id: Ie7a481c7b90583981e7d68f6a54dfb0e6f1796dd
This commit is contained in:
parent
708b9b6331
commit
77067e7b4c
@ -27,6 +27,10 @@ resources:
|
||||
description: OCI image for OpenStack designate
|
||||
upstream-source: ghcr.io/canonical/designate-consolidated:2024.1
|
||||
|
||||
provides:
|
||||
dnsaas:
|
||||
interface: designate
|
||||
|
||||
requires:
|
||||
database:
|
||||
interface: mysql_client
|
||||
|
@ -40,6 +40,10 @@ import ops_sunbeam.core as sunbeam_core
|
||||
import ops_sunbeam.guard as sunbeam_guard
|
||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
||||
import tenacity
|
||||
from charms.designate_k8s.v0.designate_service import (
|
||||
DesignateEndpointRequestEvent,
|
||||
DesignateServiceProvides,
|
||||
)
|
||||
from ops.main import (
|
||||
main,
|
||||
)
|
||||
@ -171,6 +175,42 @@ class DesignatePebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
||||
super().init_service(context)
|
||||
|
||||
|
||||
class DesignateServiceProvidesHandler(sunbeam_rhandlers.RelationHandler):
|
||||
"""Handler for designate service relation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
charm: ops.CharmBase,
|
||||
relation_name: str,
|
||||
callback_f: Callable,
|
||||
):
|
||||
super().__init__(charm, relation_name, callback_f)
|
||||
|
||||
def setup_event_handler(self):
|
||||
"""Configure event handlers for an Ceilometer service relation."""
|
||||
logger.debug("Setting up Ceilometer service event handler")
|
||||
svc = DesignateServiceProvides(
|
||||
self.charm,
|
||||
self.relation_name,
|
||||
)
|
||||
self.framework.observe(
|
||||
svc.on.endpoint_request,
|
||||
self._on_endpoint_request,
|
||||
)
|
||||
return svc
|
||||
|
||||
def _on_endpoint_request(
|
||||
self, event: DesignateEndpointRequestEvent
|
||||
) -> None:
|
||||
"""Handle endpoint request event."""
|
||||
self.callback_f(event)
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
"""Report if relation is ready."""
|
||||
return True
|
||||
|
||||
|
||||
class BindRndcRequiresRelationHandler(sunbeam_rhandlers.RelationHandler):
|
||||
"""Relation handler class."""
|
||||
|
||||
@ -371,6 +411,13 @@ class DesignateOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||
"""Relation handlers for the service."""
|
||||
handlers = handlers or []
|
||||
if self.can_add_handler("dnsaas", handlers):
|
||||
self.dnsaas = DesignateServiceProvidesHandler(
|
||||
self,
|
||||
"dnsaas",
|
||||
self.set_dns_endpoint_from_event,
|
||||
)
|
||||
handlers.append(self.dnsaas)
|
||||
if self.can_add_handler(BIND_RNDC_RELATION, handlers):
|
||||
self.bind_rndc = BindRndcRequiresRelationHandler(
|
||||
self,
|
||||
@ -481,6 +528,36 @@ class DesignateOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
except ops.SecretNotFoundError:
|
||||
return None
|
||||
|
||||
def _ingress_changed(self, event: ops.framework.EventBase) -> None:
|
||||
"""Ingress changed callback.
|
||||
|
||||
Invoked when the data on the ingress relation has changed. This will
|
||||
update the relevant endpoints with the identity service, and then
|
||||
call the configure_charm.
|
||||
"""
|
||||
self.set_dns_endpoint_on_update()
|
||||
super()._ingress_changed(event)
|
||||
|
||||
def set_dns_endpoint_from_event(
|
||||
self, event: ops.framework.EventBase
|
||||
) -> None:
|
||||
"""Set endpoint in relation data."""
|
||||
if self.internal_url:
|
||||
self.dnsaas.interface.set_endpoint(
|
||||
relation=event.relation, endpoint=self.internal_url
|
||||
)
|
||||
else:
|
||||
logging.debug("DNS Endpoint not yet set, not sending config")
|
||||
|
||||
def set_dns_endpoint_on_update(self) -> None:
|
||||
"""Set endpoint on relation on update of local data."""
|
||||
if self.internal_url:
|
||||
self.dnsaas.interface.set_endpoint(
|
||||
relation=None, endpoint=self.internal_url
|
||||
)
|
||||
else:
|
||||
logging.debug("DNS Endpoint not yet set, not sending config")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(DesignateOperatorCharm)
|
||||
|
@ -73,3 +73,29 @@ options:
|
||||
.
|
||||
Use this if a subset of your flat or VLAN provider networks have a MTU
|
||||
that differ with what is set in global-physnet-mtu.
|
||||
reverse-dns-lookup:
|
||||
default: False
|
||||
description: |
|
||||
A boolean value specifying whether to enable or not the creation of
|
||||
reverse lookup (PTR) records.
|
||||
.
|
||||
NOTE: Use only when integrating neutron-k8s charm to designate charm.
|
||||
type: boolean
|
||||
ipv4-ptr-zone-prefix-size:
|
||||
default: 24
|
||||
description: |
|
||||
The size in bits of the prefix for the IPv4 reverse lookup (PTR) zones.
|
||||
Valid size has to be multiple of 8, with maximum value of 24 and minimum
|
||||
value of 8.
|
||||
.
|
||||
NOTE: Use only when "reverse-dns-lookup" option is set to "True".
|
||||
type: int
|
||||
ipv6-ptr-zone-prefix-size:
|
||||
default: 64
|
||||
description: |
|
||||
The size in bits of the prefix for the IPv6 reverse lookup (PTR) zones.
|
||||
Valid size has to be multiple of 4, with maximum value of 124 and minimum
|
||||
value of 4.
|
||||
.
|
||||
NOTE: Use only when "reverse-dns-lookup" option is set to "True".
|
||||
type: int
|
||||
|
@ -60,6 +60,9 @@ requires:
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
external-dns:
|
||||
interface: designate
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -21,7 +21,12 @@ This charm provide Neutron services as part of an OpenStack deployment
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import (
|
||||
Callable,
|
||||
List,
|
||||
)
|
||||
|
||||
import charms.designate_k8s.v0.designate_service as designate_svc
|
||||
import ops
|
||||
import ops_sunbeam.charm as sunbeam_charm
|
||||
import ops_sunbeam.config_contexts as sunbeam_ctxts
|
||||
@ -37,10 +42,81 @@ from ops.framework import (
|
||||
from ops.main import (
|
||||
main,
|
||||
)
|
||||
from ops.model import (
|
||||
BlockedStatus,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DesignateServiceRequiresHandler(sunbeam_rhandlers.RelationHandler):
|
||||
"""Handle external-dns relation on the requires side."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
charm: ops.charm.CharmBase,
|
||||
relation_name: str,
|
||||
callback_f: Callable,
|
||||
mandatory: bool = False,
|
||||
):
|
||||
"""Create a new external-dns handler.
|
||||
|
||||
Create a new DesignateServiceRequiresHandler that handles initial
|
||||
events from the relation and invokes the provided callbacks based on
|
||||
the event raised.
|
||||
|
||||
:param charm: the Charm class the handler is for
|
||||
:type charm: ops.charm.CharmBase
|
||||
:param relation_name: the relation the handler is bound to
|
||||
:type relation_name: str
|
||||
:param callback_f: the function to call when the nodes are connected
|
||||
:type callback_f: Callable
|
||||
:param mandatory: If the relation is mandatory to proceed with
|
||||
configuring charm
|
||||
:type mandatory: bool
|
||||
"""
|
||||
super().__init__(charm, relation_name, callback_f, mandatory)
|
||||
|
||||
def setup_event_handler(self) -> None:
|
||||
"""Configure event handlers for external-dns service relation."""
|
||||
logger.debug("Setting up Designate service event handler")
|
||||
svc = designate_svc.DesignateServiceRequires(
|
||||
self.charm,
|
||||
self.relation_name,
|
||||
)
|
||||
self.framework.observe(
|
||||
svc.on.endpoint_changed,
|
||||
self._on_endpoint_changed,
|
||||
)
|
||||
self.framework.observe(
|
||||
svc.on.goneaway,
|
||||
self._on_goneaway,
|
||||
)
|
||||
return svc
|
||||
|
||||
def _on_endpoint_changed(self, event: ops.framework.EventBase) -> None:
|
||||
"""Handle endpoint_changed event."""
|
||||
logger.debug(
|
||||
"Designate service provider endpoint changed event received"
|
||||
)
|
||||
self.callback_f(event)
|
||||
|
||||
def _on_goneaway(self, event: ops.framework.EventBase) -> None:
|
||||
"""Handle gone_away event."""
|
||||
logger.debug("Designate service relation is departed/broken")
|
||||
self.callback_f(event)
|
||||
if self.mandatory:
|
||||
self.status.set(BlockedStatus("integration missing"))
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
"""Whether handler is ready for use."""
|
||||
try:
|
||||
return bool(self.interface.endpoint)
|
||||
except (AttributeError, KeyError):
|
||||
return False
|
||||
|
||||
|
||||
class NeutronServerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"""Handler for interacting with pebble data."""
|
||||
|
||||
@ -127,6 +203,7 @@ class NeutronOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
"""Check a configuration key is correct."""
|
||||
try:
|
||||
self._validate_domain()
|
||||
self._validate_ptr_zone_prefix_size()
|
||||
except ValueError as e:
|
||||
raise sunbeam_guard.BlockedExceptionError(str(e)) from e
|
||||
|
||||
@ -175,11 +252,48 @@ class NeutronOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
" and hyphens (-)"
|
||||
)
|
||||
|
||||
def _validate_ptr_zone_prefix_size(self):
|
||||
"""Check given ptr zone prefix size is valid."""
|
||||
ipv4_prefix_size = self.config.get("ipv4-ptr-zone-prefix-size")
|
||||
valid_ipv4_prefix_size = (8 <= ipv4_prefix_size <= 24) and (
|
||||
ipv4_prefix_size % 8
|
||||
) == 0
|
||||
if not valid_ipv4_prefix_size:
|
||||
raise ValueError(
|
||||
"Invalid ipv4-ptr-zone-prefix-size. Value should be between 8 - 24 and multiple of 8"
|
||||
)
|
||||
|
||||
ipv6_prefix_size = self.config.get("ipv6-ptr-zone-prefix-size")
|
||||
valid_ipv6_prefix_size = (4 <= ipv6_prefix_size <= 124) and (
|
||||
ipv6_prefix_size % 4
|
||||
) == 0
|
||||
if not valid_ipv6_prefix_size:
|
||||
raise ValueError(
|
||||
"Invalid ipv6-ptr-zone-prefix-size. Value should be between 4 - 124 and multiple of 4"
|
||||
)
|
||||
|
||||
def configure_unit(self, event: ops.EventBase) -> None:
|
||||
"""Run configuration on this unit."""
|
||||
self.check_configuration(event)
|
||||
return super().configure_unit(event)
|
||||
|
||||
def get_relation_handlers(
|
||||
self, handlers: List[sunbeam_rhandlers.RelationHandler] = None
|
||||
) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||
"""Relation handlers for the service."""
|
||||
handlers = handlers or []
|
||||
if self.can_add_handler("external-dns", handlers):
|
||||
self.external_dns = DesignateServiceRequiresHandler(
|
||||
self,
|
||||
"external-dns",
|
||||
self.configure_charm,
|
||||
"external-dns" in self.mandatory_relations,
|
||||
)
|
||||
handlers.append(self.external_dns)
|
||||
|
||||
handlers = super().get_relation_handlers(handlers)
|
||||
return handlers
|
||||
|
||||
def get_pebble_handlers(self) -> list[sunbeam_chandlers.PebbleHandler]:
|
||||
"""Pebble handlers for the service."""
|
||||
return [
|
||||
|
@ -28,6 +28,10 @@ global_physnet_mtu = {{ options.global_physnet_mtu }}
|
||||
|
||||
transport_url = {{ amqp.transport_url }}
|
||||
|
||||
{% if external_dns and external_dns.endpoint -%}
|
||||
external_dns_driver = designate
|
||||
{% endif -%}
|
||||
|
||||
[oslo_concurrency]
|
||||
lock_path = $state_path/lock
|
||||
|
||||
@ -69,3 +73,22 @@ username = {{ identity_service.service_user_name }}
|
||||
password = {{ identity_service.service_password }}
|
||||
|
||||
{% include "parts/section-oslo-messaging-rabbit" %}
|
||||
|
||||
|
||||
{% if external_dns and external_dns.endpoint -%}
|
||||
[designate]
|
||||
url = {{ external_dns.endpoint }}
|
||||
auth_type = password
|
||||
auth_url = {{ identity_service.admin_auth_url }}
|
||||
project_domain_name = {{ identity_service.service_domain_name }}
|
||||
user_domain_name = {{ identity_service.service_domain_name }}
|
||||
project_name = {{ identity_service.service_project_name }}
|
||||
username = {{ identity_service.service_user_name }}
|
||||
password = {{ identity_service.service_password }}
|
||||
allow_reverse_dns_lookup = {{ options.reverse_dns_lookup }}
|
||||
ipv4_ptr_zone_prefix_size = {{ options.ipv4_ptr_zone_prefix_size }}
|
||||
ipv6_ptr_zone_prefix_size = {{ options.ipv6_ptr_zone_prefix_size }}
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
cafile = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
|
@ -36,6 +36,7 @@ INTERNAL_CINDER_CEPH_LIBS=(
|
||||
INTERNAL_DESIGNATE_LIBS=(
|
||||
"keystone_k8s"
|
||||
"designate_bind_k8s"
|
||||
"designate_k8s"
|
||||
)
|
||||
|
||||
INTERNAL_DESIGNATE_BIND_LIBS=(
|
||||
@ -54,6 +55,7 @@ INTERNAL_KEYSTONE_LIBS=(
|
||||
INTERNAL_NEUTRON_LIBS=(
|
||||
"keystone_k8s"
|
||||
"ovn_central_k8s"
|
||||
"designate_k8s"
|
||||
)
|
||||
|
||||
INTERNAL_NOVA_LIBS=(
|
||||
|
195
libs/internal/lib/charms/designate_k8s/v0/designate_service.py
Normal file
195
libs/internal/lib/charms/designate_k8s/v0/designate_service.py
Normal file
@ -0,0 +1,195 @@
|
||||
# Copyright 2024 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
"""DesignateServiceProvides and Requires module.
|
||||
|
||||
This library contains the Requires and Provides classes for handling
|
||||
the designate interface.
|
||||
|
||||
Import `DesignateServiceRequires` in your charm, with the charm object and the
|
||||
relation name:
|
||||
- self
|
||||
- "designate"
|
||||
|
||||
Two events are also available to respond to:
|
||||
- endpoint_changed
|
||||
- goneaway
|
||||
|
||||
A basic example showing the usage of this relation follows:
|
||||
|
||||
```
|
||||
from charms.designate_k8s.v0.designate_service import (
|
||||
DesignateServiceRequires
|
||||
)
|
||||
|
||||
class DesignateServiceClientCharm(CharmBase):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
# DesignateService Requires
|
||||
self.designate_service = DesignateServiceRequires(
|
||||
self, "designate",
|
||||
)
|
||||
self.framework.observe(
|
||||
self.designate_service.on.endpoint_changed,
|
||||
self._on_designate_service_endpoint_changed
|
||||
)
|
||||
self.framework.observe(
|
||||
self.designate_service.on.goneaway,
|
||||
self._on_designate_service_goneaway
|
||||
)
|
||||
|
||||
def _on_designate_service_endpoint_changed(self, event):
|
||||
'''React to the Designate service endpoint changed event.
|
||||
|
||||
This event happens when DesignateService relation is added to the
|
||||
model and relation data is changed.
|
||||
'''
|
||||
# Do something with the configuration provided by relation.
|
||||
pass
|
||||
|
||||
def _on_designate_service_goneaway(self, event):
|
||||
'''React to the DesignateService goneaway event.
|
||||
|
||||
This event happens when DesignateService relation is removed.
|
||||
'''
|
||||
# DesignateService Relation has goneaway.
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import ops
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# The unique Charmhub library identifier, never change it
|
||||
LIBID = "3e0a3ac75f6d46a4ac5e144bbeb357e0"
|
||||
|
||||
# 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 DesignateEndpointRequestEvent(ops.RelationEvent):
|
||||
"""DesignateEndpointRequest Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DesignateServiceProviderEvents(ops.ObjectEvents):
|
||||
"""Events class for `on`."""
|
||||
|
||||
endpoint_request = ops.EventSource(DesignateEndpointRequestEvent)
|
||||
|
||||
|
||||
class DesignateServiceProvides(ops.Object):
|
||||
"""Class to be instantiated by the providing side of the relation."""
|
||||
|
||||
on = DesignateServiceProviderEvents()
|
||||
|
||||
def __init__(self, charm: ops.CharmBase, relation_name: str):
|
||||
super().__init__(charm, relation_name)
|
||||
self.charm = charm
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_changed,
|
||||
self._on_relation_changed,
|
||||
)
|
||||
|
||||
def _on_relation_changed(self, event: ops.RelationChangedEvent):
|
||||
self.on.endpoint_request.emit(event.relation)
|
||||
|
||||
def set_endpoint(
|
||||
self, relation: ops.Relation | None, endpoint: str
|
||||
) -> None:
|
||||
"""Set designate endpoint on the relation."""
|
||||
if not self.charm.unit.is_leader():
|
||||
logging.debug("Not a leader unit, skipping setting endpoint")
|
||||
return
|
||||
|
||||
# If relation is not provided send endpoint to all the related
|
||||
# applications. This happens usually when endpoint data is
|
||||
# updated by provider and wants to send the data to all
|
||||
# related applications
|
||||
if relation is None:
|
||||
logging.debug(
|
||||
"Sending endpoint 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]["endpoint"] = endpoint
|
||||
else:
|
||||
logging.debug(
|
||||
f"Sending endpoint on relation {relation.app.name} "
|
||||
f"{relation.name}/{relation.id}"
|
||||
)
|
||||
relation.data[self.charm.app]["endpoint"] = endpoint
|
||||
|
||||
|
||||
class DesignateEndpointChangedEvent(ops.RelationEvent):
|
||||
"""DesignateEndpointChanged Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DesignateServiceGoneAwayEvent(ops.RelationEvent):
|
||||
"""DesignateServiceGoneAway Event."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DesignateServiceRequirerEvents(ops.ObjectEvents):
|
||||
"""Events class for `on`."""
|
||||
|
||||
endpoint_changed = ops.EventSource(DesignateEndpointChangedEvent)
|
||||
goneaway = ops.EventSource(DesignateServiceGoneAwayEvent)
|
||||
|
||||
|
||||
class DesignateServiceRequires(ops.Object):
|
||||
"""Class to be instantiated by the requiring side of the relation."""
|
||||
|
||||
on = DesignateServiceRequirerEvents()
|
||||
|
||||
def __init__(self, charm: ops.CharmBase, relation_name: str):
|
||||
super().__init__(charm, relation_name)
|
||||
self.charm = charm
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_changed,
|
||||
self._on_relation_changed,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.charm.on[relation_name].relation_broken,
|
||||
self._on_relation_broken,
|
||||
)
|
||||
|
||||
def _on_relation_changed(self, event: ops.RelationJoinedEvent):
|
||||
"""Handle relation changed event."""
|
||||
self.on.endpoint_changed.emit(event.relation)
|
||||
|
||||
def _on_relation_broken(self, event: ops.RelationBrokenEvent):
|
||||
"""Handle relation broken event."""
|
||||
self.on.goneaway.emit(event.relation)
|
||||
|
||||
@property
|
||||
def _designate_service_rel(self) -> ops.Relation | None:
|
||||
"""The designate service relation."""
|
||||
return self.framework.model.get_relation(self.relation_name)
|
||||
|
||||
def get_remote_app_data(self, key: str) -> str | None:
|
||||
"""Return the value for the given key from remote app data."""
|
||||
if self._designate_service_rel:
|
||||
data = self._designate_service_rel.data[
|
||||
self._designate_service_rel.app
|
||||
]
|
||||
return data.get(key)
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def endpoint(self) -> str | None:
|
||||
"""Return the designate endpoint."""
|
||||
return self.get_remote_app_data("endpoint")
|
Loading…
x
Reference in New Issue
Block a user