Repository: zalando/patroni

Issue:
Add ETCD srv_name config parametter
Please consider adding ETCD SRV name config parameter to Patroni. 

As per ETCD documentation:

> 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

we use SRV name suffixes to differentiate ETCD clusters in our private domain (around 40k users) and there is no simple way how to point Patroni to a specific ETCD cluster using SRV. 




Discussion:
It is perfectly supported, please read the docs: https://patroni.readthedocs.io/en/latest/SETTINGS.html#etcd

https://github.com/zalando/patroni/blob/c81391e3148dad86adbb79e017cf105b61c8d869/patroni/dcs/etcd.py#L287-L313
OK then, how I can configure Patroni to retrieve _etcd-server-gis._tcp.ourdomain.org SRV records from DNS? (Note the -gis part of the url) Because it is not obvious from docs or even source code.
I see, discovery-srv-name is something that didn't exist 6 years ago.
If you want to have it supported - please feel free to implement the support.

Resolving patch:
diff --git a/patroni/config.py b/patroni/config.py
--- a/patroni/config.py
+++ b/patroni/config.py
@@ -347,7 +347,7 @@ def _get_auth(name, params=None):
             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
--- a/patroni/dcs/etcd.py
+++ b/patroni/dcs/etcd.py
@@ -283,13 +283,14 @@ def get_srv_record(host):
         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 @@ def _get_machines_cache_from_config(self):
 
         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
--- a/patroni/validator.py
+++ b/patroni/validator.py
@@ -302,10 +302,11 @@ def assert_(condition, message="Wrong value"):
 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})
 }


Test patch:
diff --git a/tests/test_etcd.py b/tests/test_etcd.py
--- 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 @@ def test_get_srv_record(self):
 
     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')