Restore use of address on OVN interfaces
Some clients can't use hostnames yet so expose both address and hostname information on the ovsdb{-cms} interface type. This supports backwards compatibility with the existing reactive interfaces as well. Change-Id: Iefe8ad601b305e3fdab7548e1d3357493491d3e9
This commit is contained in:
parent
9c552e6b3a
commit
193fe99c40
@ -14,6 +14,7 @@
|
||||
|
||||
"""Base classes for defining OVN relation handlers."""
|
||||
|
||||
import ipaddress
|
||||
import itertools
|
||||
import socket
|
||||
import logging
|
||||
@ -37,6 +38,37 @@ class OVNRelationUtils():
|
||||
DB_NB_CLUSTER_PORT = 6643
|
||||
DB_SB_CLUSTER_PORT = 6644
|
||||
|
||||
def _format_addr(self, addr: str) -> str:
|
||||
"""Validate and format IP address.
|
||||
|
||||
:param addr: IPv6 or IPv4 address
|
||||
:type addr: str
|
||||
:returns: Address string, optionally encapsulated in brackets ([])
|
||||
:rtype: str
|
||||
:raises: ValueError
|
||||
"""
|
||||
ipaddr = ipaddress.ip_address(addr)
|
||||
if isinstance(ipaddr, ipaddress.IPv6Address):
|
||||
fmt = '[{}]'
|
||||
else:
|
||||
fmt = '{}'
|
||||
return fmt.format(ipaddr)
|
||||
|
||||
def _remote_addrs(self, key: str) -> Iterator[str]:
|
||||
"""Retrieve addresses published by remote units.
|
||||
|
||||
:param key: Relation data key to retrieve value from.
|
||||
:type key: str
|
||||
:returns: addresses published by remote units.
|
||||
:rtype: Iterator[str]
|
||||
"""
|
||||
for addr in self.interface.get_all_unit_values(key):
|
||||
try:
|
||||
addr = self._format_addr(addr)
|
||||
yield addr
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
def _remote_hostnames(self, key: str) -> Iterator[str]:
|
||||
"""Retrieve hostnames published by remote units.
|
||||
|
||||
@ -57,6 +89,15 @@ class OVNRelationUtils():
|
||||
"""
|
||||
return self._remote_hostnames('bound-hostname')
|
||||
|
||||
@property
|
||||
def cluster_remote_addrs(self) -> Iterator[str]:
|
||||
"""Retrieve remote addresses bound to remote endpoint.
|
||||
|
||||
:returns: addresses bound to remote endpoints.
|
||||
:rtype: Iterator[str]
|
||||
"""
|
||||
return self._remote_addrs('bound-address')
|
||||
|
||||
def db_connection_strs(
|
||||
self,
|
||||
hostnames: List[str],
|
||||
@ -132,19 +173,48 @@ class OVNRelationUtils():
|
||||
:returns: OVN Northbound OVSDB connection strings.
|
||||
:rtpye: Iterator[str]
|
||||
"""
|
||||
return self.db_connection_strs(self.cluster_remote_hostnames,
|
||||
return self.db_connection_strs(self.cluster_remote_addrs,
|
||||
self.db_nb_port)
|
||||
|
||||
@property
|
||||
def db_sb_connection_strs(self) -> Iterator[str]:
|
||||
"""Provide OVN Southbound OVSDB connection strings.
|
||||
|
||||
:returns: OVN Southbound OVSDB connection strings.
|
||||
:rtpye: Iterator[str]
|
||||
"""
|
||||
return self.db_connection_strs(self.cluster_remote_addrs,
|
||||
self.db_sb_port)
|
||||
|
||||
@property
|
||||
def db_nb_connection_hostname_strs(self) -> Iterator[str]:
|
||||
"""Provide OVN Northbound OVSDB connection strings.
|
||||
|
||||
:returns: OVN Northbound OVSDB connection strings.
|
||||
:rtpye: Iterator[str]
|
||||
"""
|
||||
return self.db_connection_strs(self.cluster_remote_hostnames,
|
||||
self.db_nb_port)
|
||||
|
||||
@property
|
||||
def db_sb_connection_hostname_strs(self) -> Iterator[str]:
|
||||
"""Provide OVN Southbound OVSDB connection strings.
|
||||
|
||||
:returns: OVN Southbound OVSDB connection strings.
|
||||
:rtpye: Iterator[str]
|
||||
"""
|
||||
return self.db_connection_strs(self.cluster_remote_hostnames,
|
||||
self.db_sb_port)
|
||||
|
||||
@property
|
||||
def cluster_local_addr(self) -> ipaddress.IPv4Address:
|
||||
"""Retrieve local address bound to endpoint.
|
||||
|
||||
:returns: IPv4 or IPv6 address bound to endpoint
|
||||
:rtype: str
|
||||
"""
|
||||
return self._endpoint_local_bound_addr()
|
||||
|
||||
@property
|
||||
def cluster_local_hostname(self) -> str:
|
||||
"""Retrieve local hostname for unit.
|
||||
@ -154,6 +224,18 @@ class OVNRelationUtils():
|
||||
"""
|
||||
return socket.getfqdn()
|
||||
|
||||
def _endpoint_local_bound_addr(self) -> ipaddress.IPv4Address:
|
||||
"""Retrieve local address bound to endpoint.
|
||||
|
||||
:returns: IPv4 or IPv6 address bound to endpoint
|
||||
"""
|
||||
addr = None
|
||||
for relation in self.charm.model.relations.get(self.relation_name, []):
|
||||
binding = self.charm.model.get_binding(relation)
|
||||
addr = binding.network.bind_address
|
||||
break
|
||||
return addr
|
||||
|
||||
|
||||
class OVNDBClusterPeerHandler(sunbeam_rhandlers.BasePeerHandler,
|
||||
OVNRelationUtils):
|
||||
@ -325,8 +407,10 @@ class OVSDBCMSProvidesHandler(sunbeam_rhandlers.RelationHandler,
|
||||
# Ready is only emitted when the interface considers
|
||||
# that the relation is complete (indicated by a password)
|
||||
# _hostname = hostname or self.cluster_local_hostname
|
||||
self.interface.set_unit_data(
|
||||
{'bound-hostname': str(self.cluster_local_hostname)})
|
||||
self.interface.set_unit_data({
|
||||
'bound-hostname': str(self.cluster_local_hostname),
|
||||
'bound-address': str(self.cluster_local_addr),
|
||||
})
|
||||
self.callback_f(event)
|
||||
|
||||
@property
|
||||
@ -379,7 +463,14 @@ class OVSDBCMSRequiresHandler(sunbeam_rhandlers.RelationHandler,
|
||||
ctxt.update({
|
||||
'local_hostname': self.cluster_local_hostname,
|
||||
'hostnames': self.interface.bound_hostnames(),
|
||||
'local_address': self.cluster_local_addr,
|
||||
'addresses': self.interface.bound_addresses(),
|
||||
'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(self.db_sb_connection_hostname_strs),
|
||||
'db_nb_connection_hostname_strs':
|
||||
','.join(self.db_nb_connection_hostname_strs)
|
||||
})
|
||||
|
||||
return ctxt
|
||||
|
@ -37,7 +37,7 @@ 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
|
||||
LIBPATCH = 3
|
||||
|
||||
|
||||
# TODO: add your code here! Happy coding!
|
||||
@ -101,11 +101,14 @@ class OVSDBCMSRequires(Object):
|
||||
logging.debug("OVSDBCMSRequires on_joined")
|
||||
self.on.connected.emit()
|
||||
|
||||
def bound_hostnames(self):
|
||||
return self.get_all_unit_values("bound-hostname")
|
||||
|
||||
def bound_addresses(self):
|
||||
return self.get_all_unit_values("bound-address")
|
||||
|
||||
def remote_ready(self):
|
||||
return all(self.bound_addresses())
|
||||
return all(self.bound_hostnames()) or all(self.bound_addresses())
|
||||
|
||||
def _on_ovsdb_cms_relation_changed(self, event):
|
||||
"""OVSDBCMS relation changed."""
|
||||
@ -201,5 +204,3 @@ class OVSDBCMSProvides(Object):
|
||||
for relation in relations:
|
||||
for k, v in settings.items():
|
||||
relation.data[self.model.unit][k] = v
|
||||
|
||||
|
||||
|
@ -54,7 +54,7 @@ class SomeCharm(CharmBase):
|
||||
import logging
|
||||
import socket
|
||||
import typing
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
import yaml
|
||||
from ops.charm import CharmBase, RelationBrokenEvent, RelationEvent
|
||||
@ -69,7 +69,7 @@ LIBAPI = 1
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 3
|
||||
LIBPATCH = 5
|
||||
|
||||
DEFAULT_RELATION_NAME = "ingress"
|
||||
RELATION_INTERFACE = "ingress"
|
||||
@ -97,6 +97,7 @@ INGRESS_REQUIRES_APP_SCHEMA = {
|
||||
"name": {"type": "string"},
|
||||
"host": {"type": "string"},
|
||||
"port": {"type": "string"},
|
||||
"strip-prefix": {"type": "string"},
|
||||
},
|
||||
"required": ["model", "name", "host", "port"],
|
||||
}
|
||||
@ -115,7 +116,11 @@ except ImportError:
|
||||
from typing_extensions import TypedDict # py35 compat
|
||||
|
||||
# Model of the data a unit implementing the requirer will need to provide.
|
||||
RequirerData = TypedDict("RequirerData", {"model": str, "name": str, "host": str, "port": int})
|
||||
RequirerData = TypedDict(
|
||||
"RequirerData",
|
||||
{"model": str, "name": str, "host": str, "port": int, "strip-prefix": bool},
|
||||
total=False,
|
||||
)
|
||||
# Provider ingress data model.
|
||||
ProviderIngressData = TypedDict("ProviderIngressData", {"url": str})
|
||||
# Provider application databag model.
|
||||
@ -221,12 +226,14 @@ class _IPAEvent(RelationEvent):
|
||||
class IngressPerAppDataProvidedEvent(_IPAEvent):
|
||||
"""Event representing that ingress data has been provided for an app."""
|
||||
|
||||
__args__ = ("name", "model", "port", "host")
|
||||
__args__ = ("name", "model", "port", "host", "strip_prefix")
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
name = None # type: str
|
||||
model = None # type: str
|
||||
port = None # type: int
|
||||
host = None # type: str
|
||||
strip_prefix = False # type: bool
|
||||
|
||||
|
||||
class IngressPerAppDataRemovedEvent(RelationEvent):
|
||||
@ -266,6 +273,7 @@ class IngressPerAppProvider(_IngressPerAppBase):
|
||||
data["model"],
|
||||
data["port"],
|
||||
data["host"],
|
||||
data.get("strip-prefix", False),
|
||||
)
|
||||
|
||||
def _handle_relation_broken(self, event):
|
||||
@ -297,17 +305,14 @@ class IngressPerAppProvider(_IngressPerAppBase):
|
||||
return {}
|
||||
|
||||
databag = relation.data[relation.app]
|
||||
try:
|
||||
remote_data = {k: databag[k] for k in ("model", "name", "host", "port")}
|
||||
except KeyError as e:
|
||||
# incomplete data / invalid data
|
||||
log.debug("error {}; ignoring...".format(e))
|
||||
return {}
|
||||
except TypeError as e:
|
||||
raise DataValidationError("Error casting remote data: {}".format(e))
|
||||
remote_data = {} # type: Dict[str, Union[int, str]]
|
||||
for k in ("port", "host", "model", "name", "mode", "strip-prefix"):
|
||||
v = databag.get(k)
|
||||
if v is not None:
|
||||
remote_data[k] = v
|
||||
_validate_data(remote_data, INGRESS_REQUIRES_APP_SCHEMA)
|
||||
|
||||
remote_data["port"] = int(remote_data["port"])
|
||||
remote_data["strip-prefix"] = bool(remote_data.get("strip-prefix", False))
|
||||
return remote_data
|
||||
|
||||
def get_data(self, relation: Relation) -> RequirerData:
|
||||
@ -408,6 +413,7 @@ class IngressPerAppRequirer(_IngressPerAppBase):
|
||||
*,
|
||||
host: str = None,
|
||||
port: int = None,
|
||||
strip_prefix: bool = False,
|
||||
):
|
||||
"""Constructor for IngressRequirer.
|
||||
|
||||
@ -422,6 +428,7 @@ class IngressPerAppRequirer(_IngressPerAppBase):
|
||||
relation must be of interface type `ingress` and have "limit: 1")
|
||||
host: Hostname to be used by the ingress provider to address the requiring
|
||||
application; if unspecified, the default Kubernetes service name will be used.
|
||||
strip_prefix: configure Traefik to strip the path prefix.
|
||||
|
||||
Request Args:
|
||||
port: the port of the service
|
||||
@ -429,6 +436,7 @@ class IngressPerAppRequirer(_IngressPerAppBase):
|
||||
super().__init__(charm, relation_name)
|
||||
self.charm: CharmBase = charm
|
||||
self.relation_name = relation_name
|
||||
self._strip_prefix = strip_prefix
|
||||
|
||||
self._stored.set_default(current_url=None)
|
||||
|
||||
@ -501,6 +509,10 @@ class IngressPerAppRequirer(_IngressPerAppBase):
|
||||
"host": host,
|
||||
"port": str(port),
|
||||
}
|
||||
|
||||
if self._strip_prefix:
|
||||
data["strip-prefix"] = "true"
|
||||
|
||||
_validate_data(data, INGRESS_REQUIRES_APP_SCHEMA)
|
||||
self.relation.data[self.app].update(data)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user