From 195b8bf0492bbb30c74bea3e4e53b4780186deba Mon Sep 17 00:00:00 2001 From: DavidPavlicek Date: Fri, 13 Aug 2021 15:49:01 +0200 Subject: [PATCH] Support for ETCD SRV name suffix (#2029) Add support for ETCD SRV name suffix as per description in ETCD dosc: > The -discovery-srv-name flag additionally configures a suffix to the SRV name that is queried during discovery. Use this flag to differentiate between multiple etcd clusters under the same domain. For example, if discovery-srv=example.com and -discovery-srv-name=foo are set, the following DNS SRV queries are made: > > _etcd-server-ssl-foo._tcp.example.com > _etcd-server-foo._tcp.example.com All test passes, but not been tested on the live ETCD system yet... Please, take a look and send feedback. Resolves #2028 --- docs/ENVIRONMENT.rst | 3 ++- docs/SETTINGS.rst | 3 ++- patroni/config.py | 2 +- patroni/dcs/etcd.py | 5 +++-- patroni/validator.py | 3 ++- tests/test_etcd.py | 4 +++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/ENVIRONMENT.rst b/docs/ENVIRONMENT.rst index 7c837a6b..43a35921 100644 --- a/docs/ENVIRONMENT.rst +++ b/docs/ENVIRONMENT.rst @@ -59,7 +59,8 @@ Etcd - **PATRONI\_ETCD\_USE\_PROXIES**: If this parameter is set to true, Patroni will consider **hosts** as a list of proxies and will not perform a topology discovery of etcd cluster but stick to a fixed list of **hosts**. - **PATRONI\_ETCD\_PROTOCOL**: http or https, if not specified http is used. If the **url** or **proxy** is specified - will take protocol from them. - **PATRONI\_ETCD\_HOST**: the host:port for the etcd endpoint. -- **PATRONI\_ETCD\_SRV**: Domain to search the SRV record(s) for cluster autodiscovery. +- **PATRONI\_ETCD\_SRV**: Domain to search the SRV record(s) for cluster autodiscovery. Patroni will try to query these SRV service names for specified domain (in that order until first success): ``_etcd-client-ssl``, ``_etcd-client``, ``_etcd-ssl``, ``_etcd``, ``_etcd-server-ssl``, ``_etcd-server``. If SRV records for ``_etcd-server-ssl`` or ``_etcd-server`` are retrieved then ETCD peer protocol is used do query ETCD for available members. Otherwise hosts from SRV records will be used. +- **PATRONI\_ETCD\_SRV\_SUFFIX**: Configures a suffix to the SRV name that is queried during discovery. Use this flag to differentiate between multiple etcd clusters under the same domain. Works only with conjunction with **PATRONI\_ETCD\_SRV**. For example, if ``PATRONI_ETCD_SRV_SUFFIX=foo`` and ``PATRONI_ETCD_SRV=example.org`` are set, the following DNS SRV query is made:``_etcd-client-ssl-foo._tcp.example.com`` (and so on for every possible ETCD SRV service name). - **PATRONI\_ETCD\_USERNAME**: username for etcd authentication. - **PATRONI\_ETCD\_PASSWORD**: password for etcd authentication. - **PATRONI\_ETCD\_CACERT**: The ca certificate. If present it will enable validation. diff --git a/docs/SETTINGS.rst b/docs/SETTINGS.rst index 111854aa..77f41c22 100644 --- a/docs/SETTINGS.rst +++ b/docs/SETTINGS.rst @@ -156,7 +156,8 @@ Most of the parameters are optional, but you have to specify one of the **host** - **use\_proxies**: If this parameter is set to true, Patroni will consider **hosts** as a list of proxies and will not perform a topology discovery of etcd cluster. - **url**: url for the etcd. - **proxy**: proxy url for the etcd. If you are connecting to the etcd using proxy, use this parameter instead of **url**. -- **srv**: Domain to search the SRV record(s) for cluster autodiscovery. +- **srv**: Domain to search the SRV record(s) for cluster autodiscovery. Patroni will try to query these SRV service names for specified domain (in that order until first success): ``_etcd-client-ssl``, ``_etcd-client``, ``_etcd-ssl``, ``_etcd``, ``_etcd-server-ssl``, ``_etcd-server``. If SRV records for ``_etcd-server-ssl`` or ``_etcd-server`` are retrieved then ETCD peer protocol is used do query ETCD for available members. Otherwise hosts from SRV records will be used. +- **srv\_suffix**: Configures a suffix to the SRV name that is queried during discovery. Use this flag to differentiate between multiple etcd clusters under the same domain. Works only with conjunction with **srv**. For example, if ``srv_suffix: foo`` and ``srv: example.org`` are set, the following DNS SRV query is made:``_etcd-client-ssl-foo._tcp.example.com`` (and so on for every possible ETCD SRV service name). - **protocol**: (optional) http or https, if not specified http is used. If the **url** or **proxy** is specified - will take protocol from them. - **username**: (optional) username for etcd authentication. - **password**: (optional) password for etcd authentication. diff --git a/patroni/config.py b/patroni/config.py index 1308cc49..d007fa6c 100644 --- a/patroni/config.py +++ b/patroni/config.py @@ -347,7 +347,7 @@ class Config(object): if param.startswith(PATRONI_ENV_PREFIX): # PATRONI_(ETCD|CONSUL|ZOOKEEPER|EXHIBITOR|...)_(HOSTS?|PORT|..) name, suffix = (param[8:].split('_', 1) + [''])[:2] - if suffix in ('HOST', 'HOSTS', 'PORT', 'USE_PROXIES', 'PROTOCOL', 'SRV', 'URL', 'PROXY', + if suffix in ('HOST', 'HOSTS', 'PORT', 'USE_PROXIES', 'PROTOCOL', 'SRV', 'SRV_SUFFIX', 'URL', 'PROXY', 'CACERT', 'CERT', 'KEY', 'VERIFY', 'TOKEN', 'CHECKS', 'DC', 'CONSISTENCY', 'REGISTER_SERVICE', 'SERVICE_CHECK_INTERVAL', 'NAMESPACE', 'CONTEXT', 'USE_ENDPOINTS', 'SCOPE_LABEL', 'ROLE_LABEL', 'POD_IP', 'PORTS', 'LABELS', diff --git a/patroni/dcs/etcd.py b/patroni/dcs/etcd.py index 198dede2..aef0d2b2 100644 --- a/patroni/dcs/etcd.py +++ b/patroni/dcs/etcd.py @@ -283,13 +283,14 @@ class AbstractEtcdClientWithFailover(etcd.Client): except DNSException: return [] - def _get_machines_cache_from_srv(self, srv): + def _get_machines_cache_from_srv(self, srv, srv_suffix=None): """Fetch list of etcd-cluster member by resolving _etcd-server._tcp. SRV record. This record should contain list of host and peer ports which could be used to run 'GET http://{host}:{port}/members' request (peer protocol)""" ret = [] for r in ['-client-ssl', '-client', '-ssl', '', '-server-ssl', '-server']: + r = r.format('{0}-{1}', r, srv_suffix) if srv_suffix else r protocol = 'https' if '-ssl' in r else 'http' endpoint = '/members' if '-server' in r else '' for host, port in self.get_srv_record('_etcd{0}._tcp.{1}'.format(r, srv)): @@ -326,7 +327,7 @@ class AbstractEtcdClientWithFailover(etcd.Client): machines_cache = [] if 'srv' in self._config: - machines_cache = self._get_machines_cache_from_srv(self._config['srv']) + machines_cache = self._get_machines_cache_from_srv(self._config['srv'], self._config.get('srv_suffix')) if not machines_cache and 'hosts' in self._config: machines_cache = list(self._config['hosts']) diff --git a/patroni/validator.py b/patroni/validator.py index e7aed603..6d2a060c 100644 --- a/patroni/validator.py +++ b/patroni/validator.py @@ -302,10 +302,11 @@ validate_host_port_listen.expected_type = string_types validate_host_port_listen_multiple_hosts.expected_type = string_types validate_data_dir.expected_type = string_types validate_etcd = { - Or("host", "hosts", "srv", "url", "proxy"): Case({ + Or("host", "hosts", "srv", "srv_suffix", "url", "proxy"): Case({ "host": validate_host_port, "hosts": Or(comma_separated_host_port, [validate_host_port]), "srv": str, + "srv_suffix": str, "url": str, "proxy": str}) } diff --git a/tests/test_etcd.py b/tests/test_etcd.py index b96082ae..c631bd90 100644 --- a/tests/test_etcd.py +++ b/tests/test_etcd.py @@ -87,7 +87,8 @@ def dns_query(name, _): raise DNSException() srv = Mock() srv.port = 2380 - srv.target.to_text.return_value = 'localhost' if name == '_etcd-server._tcp.foobar' else '127.0.0.1' + srv.target.to_text.return_value = \ + 'localhost' if name in ['_etcd-server._tcp.foobar', '_etcd-server-baz._tcp.foobar'] else '127.0.0.1' return [srv] @@ -183,6 +184,7 @@ class TestClient(unittest.TestCase): def test__get_machines_cache_from_srv(self): self.client._get_machines_cache_from_srv('foobar') + self.client._get_machines_cache_from_srv('foobar', 'baz') self.client.get_srv_record = Mock(return_value=[('localhost', 2380)]) self.client._get_machines_cache_from_srv('blabla')