Implement possibility to configure retry_timeout globally

Previously it was hardcoded all over the place.
This commit is contained in:
Alexander Kukushkin
2016-05-31 10:30:53 +02:00
parent 515e9e34f4
commit b3ada161cf
14 changed files with 43 additions and 21 deletions
+1
View File
@@ -57,6 +57,7 @@ class Patroni(object):
self.tags = self.get_tags() self.tags = self.get_tags()
self.nap_time = self.config['loop_wait'] self.nap_time = self.config['loop_wait']
self.dcs.set_ttl(self.config.get('ttl') or 30) self.dcs.set_ttl(self.config.get('ttl') or 30)
self.dcs.set_retry_timeout(self.config.get('retry_timeout') or self.nap_time)
self.api.reload_config(self.config['restapi']) self.api.reload_config(self.config['restapi'])
self.postgresql.reload_config(self.config['postgresql']) self.postgresql.reload_config(self.config['postgresql'])
except Exception: except Exception:
+1 -1
View File
@@ -32,7 +32,7 @@ class Config(object):
__CACHE_FILENAME = 'patroni.dynamic.json' __CACHE_FILENAME = 'patroni.dynamic.json'
__DEFAULT_CONFIG = { __DEFAULT_CONFIG = {
'ttl': 30, 'loop_wait': 10, 'retry_timeout': 5, 'ttl': 30, 'loop_wait': 10, 'retry_timeout': 10,
'maximum_lag_on_failover': 1048576, 'maximum_lag_on_failover': 1048576,
'postgresql': { 'postgresql': {
'parameters': Postgresql.CMDLINE_OPTIONS 'parameters': Postgresql.CMDLINE_OPTIONS
+4
View File
@@ -265,6 +265,10 @@ class AbstractDCS(object):
def set_ttl(self, ttl): def set_ttl(self, ttl):
"""Set the new ttl value for leader key""" """Set the new ttl value for leader key"""
@abc.abstractmethod
def set_retry_timeout(self, retry_timeout):
"""Set the new value for retry_timeout"""
@abc.abstractmethod @abc.abstractmethod
def _load_cluster(self): def _load_cluster(self):
"""Internally this method should build `Cluster` object which """Internally this method should build `Cluster` object which
+6 -3
View File
@@ -21,9 +21,8 @@ class HTTPClient(std.HTTPClient):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(HTTPClient, self).__init__(*args, **kwargs) super(HTTPClient, self).__init__(*args, **kwargs)
self._patch_default_timeout()
def _patch_default_timeout(self): def patch_default_timeout(self, timeout):
# Set a default timeout for the `request.session.request` method, that is used # Set a default timeout for the `request.session.request` method, that is used
# internally by the methods request.session.get, request.session.post and # internally by the methods request.session.get, request.session.post and
# others. We monkey-patch here to avoid reimplementing each individual method from # others. We monkey-patch here to avoid reimplementing each individual method from
@@ -78,6 +77,7 @@ class Consul(AbstractDCS):
self.set_ttl(config.get('ttl') or 30) self.set_ttl(config.get('ttl') or 30)
host, port = config.get('host', '127.0.0.1:8500').split(':') host, port = config.get('host', '127.0.0.1:8500').split(':')
self._client = ConsulClient(host=host, port=port) self._client = ConsulClient(host=host, port=port)
self._client.http.patch_default_timeout(config['retry_timeout']/2.0)
self._scope = config['scope'] self._scope = config['scope']
self.create_or_restore_session() self.create_or_restore_session()
@@ -93,7 +93,7 @@ class Consul(AbstractDCS):
sleep(5) sleep(5)
def set_ttl(self, ttl): def set_ttl(self, ttl):
ttl = int(ttl/2) # My experiments have shown that session expires after 2*ttl time ttl = ttl/2.0 # My experiments have shown that session expires after 2*ttl time
if self._ttl != ttl: if self._ttl != ttl:
if self._session: if self._session:
try: try:
@@ -107,6 +107,9 @@ class Consul(AbstractDCS):
self.event.set() self.event.set()
self._ttl = ttl self._ttl = ttl
def set_retry_timeout(self, retry_timeout):
self._client.http.patch_default_timeout(retry_timeout/2.0)
def refresh_session(self): def refresh_session(self):
""":returns: `!True` if it had to create new session""" """:returns: `!True` if it had to create new session"""
if self._session: if self._session:
+4 -1
View File
@@ -194,7 +194,7 @@ class Etcd(AbstractDCS):
def __init__(self, config): def __init__(self, config):
super(Etcd, self).__init__(config) super(Etcd, self).__init__(config)
self._ttl = int(config.get('ttl') or 30) self._ttl = int(config.get('ttl') or 30)
self._retry = Retry(deadline=10, max_delay=1, max_tries=-1, self._retry = Retry(deadline=config['retry_timeout'], max_delay=1, max_tries=-1,
retry_exceptions=(etcd.EtcdConnectionFailed, retry_exceptions=(etcd.EtcdConnectionFailed,
etcd.EtcdLeaderElectionInProgress, etcd.EtcdLeaderElectionInProgress,
etcd.EtcdWatcherCleared, etcd.EtcdWatcherCleared,
@@ -224,6 +224,9 @@ class Etcd(AbstractDCS):
self.event.set() self.event.set()
self._ttl = ttl self._ttl = ttl
def set_retry_timeout(self, retry_timeout):
self._retry.deadline = retry_timeout
@staticmethod @staticmethod
def member(node): def member(node):
return Member.from_node(node.modifiedIndex, os.path.basename(node.key), node.ttl, node.value) return Member.from_node(node.modifiedIndex, os.path.basename(node.key), node.ttl, node.value)
+7 -5
View File
@@ -83,9 +83,8 @@ class ZooKeeper(AbstractDCS):
self.exhibitor = ExhibitorEnsembleProvider(exhibitor['hosts'], exhibitor['port'], poll_interval=interval) self.exhibitor = ExhibitorEnsembleProvider(exhibitor['hosts'], exhibitor['port'], poll_interval=interval)
hosts = self.exhibitor.zookeeper_hosts hosts = self.exhibitor.zookeeper_hosts
self._client = KazooClient(hosts=hosts, timeout=(config.get('session_timeout') or config.get('ttl') or 30), self._client = KazooClient(hosts=hosts, timeout=config['ttl'],
command_retry={'deadline': (config.get('reconnect_timeout') or 10), command_retry={'deadline': config['retry_timeout'], 'max_delay': 1, 'max_tries': -1},
'max_delay': 1, 'max_tries': -1},
connection_retry={'max_delay': 1, 'max_tries': -1}) connection_retry={'max_delay': 1, 'max_tries': -1})
self._client.add_listener(self.session_listener) self._client.add_listener(self.session_listener)
@@ -105,12 +104,15 @@ class ZooKeeper(AbstractDCS):
def set_ttl(self, ttl): def set_ttl(self, ttl):
ttl = int(ttl * 1000) ttl = int(ttl * 1000)
# I know, it's weird to access private attributes and method # I know, it's weird to access private attributes, but there is
# but there is no other way to change session_timeout without losing session # no other way to change session_timeout without losing session
if self._client._session_timeout != ttl: if self._client._session_timeout != ttl:
self._client._session_timeout = ttl self._client._session_timeout = ttl
self._client.restart() self._client.restart()
def set_retry_timeout(self, retry_timeout):
self._client._retry.deadline = retry_timeout
def get_node(self, key, watch=None): def get_node(self, key, watch=None):
try: try:
ret = self._client.get(key, watch) ret = self._client.get(key, watch)
+3 -1
View File
@@ -95,7 +95,8 @@ class Postgresql(object):
self._cursor_holder = None self._cursor_holder = None
self._sysid = None self._sysid = None
self._replication_slots = [] # list of already existing replication slots self._replication_slots = [] # list of already existing replication slots
self.retry = Retry(max_tries=-1, deadline=5, max_delay=1, retry_exceptions=PostgresConnectionException) self.retry = Retry(max_tries=-1, deadline=config['retry_timeout']/2.0, max_delay=1,
retry_exceptions=PostgresConnectionException)
self._state_lock = Lock() self._state_lock = Lock()
self.set_state('stopped') self.set_state('stopped')
@@ -160,6 +161,7 @@ class Postgresql(object):
if reload_pending: if reload_pending:
self._write_postgresql_conf() self._write_postgresql_conf()
self.reload() self.reload()
self.retry.deadline = config['retry_timeout']/2.0
@property @property
def restart_pending(self): def restart_pending(self):
+1 -1
View File
@@ -15,7 +15,7 @@ bootstrap:
dcs: dcs:
ttl: 30 ttl: 30
loop_wait: 10 loop_wait: 10
retry_timeout: 5 retry_timeout: 10
maximum_lag_on_failover: 1048576 maximum_lag_on_failover: 1048576
postgresql: postgresql:
use_pg_rewind: true use_pg_rewind: true
+4 -1
View File
@@ -51,7 +51,7 @@ class TestConsul(unittest.TestCase):
@patch.object(consul.Consul.KV, 'get', kv_get) @patch.object(consul.Consul.KV, 'get', kv_get)
@patch.object(consul.Consul.KV, 'delete', Mock()) @patch.object(consul.Consul.KV, 'delete', Mock())
def setUp(self): def setUp(self):
self.c = Consul({'ttl': 30, 'scope': 'test', 'name': 'postgresql1', 'host': 'localhost:1'}) self.c = Consul({'ttl': 30, 'scope': 'test', 'name': 'postgresql1', 'host': 'localhost:1', 'retry_timeout': 10})
self.c._base_path = '/service/good' self.c._base_path = '/service/good'
self.c._load_cluster() self.c._load_cluster()
@@ -133,3 +133,6 @@ class TestConsul(unittest.TestCase):
@patch.object(consul.Consul.Session, 'destroy', Mock(side_effect=ConsulException)) @patch.object(consul.Consul.Session, 'destroy', Mock(side_effect=ConsulException))
def test_set_ttl(self): def test_set_ttl(self):
self.c.set_ttl(20) self.c.set_ttl(20)
def test_set_retry_timeout(self):
self.c.set_retry_timeout(10)
+1 -1
View File
@@ -51,7 +51,7 @@ class TestCtl(unittest.TestCase):
self.runner = CliRunner() self.runner = CliRunner()
with patch.object(etcd.Client, 'machines') as mock_machines: with patch.object(etcd.Client, 'machines') as mock_machines:
mock_machines.__get__ = Mock(return_value=['http://remotehost:2379']) mock_machines.__get__ = Mock(return_value=['http://remotehost:2379'])
self.e = get_dcs({'etcd': {'ttl': 30, 'host': 'ok:2379'}}, 'foo') self.e = get_dcs({'etcd': {'ttl': 30, 'host': 'ok:2379', 'retry_timeout': 10}}, 'foo')
@patch('psycopg2.connect', psycopg2_connect) @patch('psycopg2.connect', psycopg2_connect)
def test_get_cursor(self): def test_get_cursor(self):
+1 -1
View File
@@ -193,7 +193,7 @@ class TestEtcd(unittest.TestCase):
def setUp(self): def setUp(self):
with patch.object(Client, 'machines') as mock_machines: with patch.object(Client, 'machines') as mock_machines:
mock_machines.__get__ = Mock(return_value=['http://localhost:2379', 'http://localhost:4001']) mock_machines.__get__ = Mock(return_value=['http://localhost:2379', 'http://localhost:4001'])
self.etcd = Etcd({'namespace': '/patroni/', 'ttl': 30, self.etcd = Etcd({'namespace': '/patroni/', 'ttl': 30, 'retry_timeout': 10,
'host': 'localhost:2379', 'scope': 'test', 'name': 'foo'}) 'host': 'localhost:2379', 'scope': 'test', 'name': 'foo'})
def test_base_path(self): def test_base_path(self):
+3 -2
View File
@@ -102,7 +102,7 @@ class TestHa(unittest.TestCase):
with patch.object(etcd.Client, 'machines') as mock_machines: with patch.object(etcd.Client, 'machines') as mock_machines:
mock_machines.__get__ = Mock(return_value=['http://remotehost:2379']) mock_machines.__get__ = Mock(return_value=['http://remotehost:2379'])
self.p = Postgresql({'name': 'postgresql0', 'scope': 'dummy', 'listen': '127.0.0.1:5432', self.p = Postgresql({'name': 'postgresql0', 'scope': 'dummy', 'listen': '127.0.0.1:5432',
'data_dir': 'data/postgresql0', 'data_dir': 'data/postgresql0', 'retry_timeout': 10,
'authentication': {'superuser': {'username': 'foo', 'password': 'bar'}, 'authentication': {'superuser': {'username': 'foo', 'password': 'bar'},
'replication': {'username': '', 'password': ''}}, 'replication': {'username': '', 'password': ''}},
'parameters': {'wal_level': 'hot_standby', 'max_replication_slots': 5, 'foo': 'bar', 'parameters': {'wal_level': 'hot_standby', 'max_replication_slots': 5, 'foo': 'bar',
@@ -111,7 +111,8 @@ class TestHa(unittest.TestCase):
self.p.set_role('replica') self.p.set_role('replica')
self.p.check_replication_lag = true self.p.check_replication_lag = true
self.p.can_create_replica_without_replication_connection = MagicMock(return_value=False) self.p.can_create_replica_without_replication_connection = MagicMock(return_value=False)
self.e = get_dcs({'etcd': {'ttl': 30, 'host': 'ok:2379', 'scope': 'test', 'name': 'foo'}}) self.e = get_dcs({'etcd': {'ttl': 30, 'host': 'ok:2379', 'scope': 'test',
'name': 'foo', 'retry_timeout': 10}})
self.ha = Ha(MockPatroni(self.p, self.e)) self.ha = Ha(MockPatroni(self.p, self.e))
self.ha._async_executor.run_async = run_async self.ha._async_executor.run_async = run_async
self.ha.old_cluster = self.e.get_cluster() self.ha.old_cluster = self.e.get_cluster()
+3 -3
View File
@@ -160,7 +160,7 @@ class TestPostgresql(unittest.TestCase):
self.data_dir = 'data/test0' self.data_dir = 'data/test0'
if not os.path.exists(self.data_dir): if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir) os.makedirs(self.data_dir)
self.p = Postgresql({'name': 'test0', 'scope': 'batman', 'data_dir': self.data_dir, self.p = Postgresql({'name': 'test0', 'scope': 'batman', 'data_dir': self.data_dir, 'retry_timeout': 10,
'listen': '127.0.0.1, *:5432', 'connect_address': '127.0.0.2:5432', 'listen': '127.0.0.1, *:5432', 'connect_address': '127.0.0.2:5432',
'authentication': {'superuser': {'username': 'test', 'password': 'test'}, 'authentication': {'superuser': {'username': 'test', 'password': 'test'},
'replication': {'username': 'replicator', 'password': 'rep-pass'}}, 'replication': {'username': 'replicator', 'password': 'rep-pass'}},
@@ -475,8 +475,8 @@ class TestPostgresql(unittest.TestCase):
self.assertFalse(self.p.replica_method_can_work_without_replication_connection('foo')) self.assertFalse(self.p.replica_method_can_work_without_replication_connection('foo'))
def test_reload_config(self): def test_reload_config(self):
self.p.reload_config({'listen': '*', 'parameters': self._PARAMETERS}) self.p.reload_config({'retry_timeout': 10, 'listen': '*', 'parameters': self._PARAMETERS})
self.p.reload_config({'listen': '*:5433', 'parameters': self._PARAMETERS}) self.p.reload_config({'retry_timeout': 10, 'listen': '*:5433', 'parameters': self._PARAMETERS})
@patch.object(builtins, 'open', mock_open(read_data='9.4')) @patch.object(builtins, 'open', mock_open(read_data='9.4'))
def test_get_major_version(self): def test_get_major_version(self):
+4 -1
View File
@@ -104,7 +104,7 @@ class TestZooKeeper(unittest.TestCase):
@patch('patroni.dcs.zookeeper.KazooClient', MockKazooClient) @patch('patroni.dcs.zookeeper.KazooClient', MockKazooClient)
def setUp(self): def setUp(self):
self.zk = ZooKeeper({'exhibitor': {'hosts': ['localhost', 'exhibitor'], 'port': 8181}, self.zk = ZooKeeper({'exhibitor': {'hosts': ['localhost', 'exhibitor'], 'port': 8181},
'scope': 'test', 'name': 'foo'}) 'scope': 'test', 'name': 'foo', 'ttl': 30, 'retry_timeout': 10})
def test_session_listener(self): def test_session_listener(self):
self.zk.session_listener(KazooState.SUSPENDED) self.zk.session_listener(KazooState.SUSPENDED)
@@ -112,6 +112,9 @@ class TestZooKeeper(unittest.TestCase):
def test_set_ttl(self): def test_set_ttl(self):
self.zk.set_ttl(20) self.zk.set_ttl(20)
def test_set_retry_timeout(self):
self.zk.set_retry_timeout(10)
def test_get_node(self): def test_get_node(self):
self.assertIsNone(self.zk.get_node('/no_node')) self.assertIsNone(self.zk.get_node('/no_node'))