Compare commits

...
3 Commits
Author SHA1 Message Date
Alexander KukushkinandGitHub 534829d617 Release 1.5.0 (#809)
Update release notes and bump version
2018-09-20 16:29:00 +02:00
Alexander KukushkinandGitHub 76d1b4cfd8 Minor fixes (#808)
* Use `shutil.move` instead of `os.replace`, which is available only from 3.3
*  Introduce standby-leader health-check and consul service
* Improve unit tests, some lines were not covered
* rename `assertEquals` -> `assertEqual`, due to deprecation warning
2018-09-19 16:32:33 +02:00
Pavel GolubandAlexander Kukushkin 3d76a013a7 Support for Windows (#799)
Postgres on Windows is using different signals, backslashes as file separators and some of the functions and syscalls are not available there.
2018-09-19 13:50:36 +02:00
23 changed files with 309 additions and 235 deletions
+2
View File
@@ -154,6 +154,8 @@ REST API
- **certfile**: Specifies the file with the certificate in the PEM format. If the certfile is not specified or is left empty, the API server will work without SSL. - **certfile**: Specifies the file with the certificate in the PEM format. If the certfile is not specified or is left empty, the API server will work without SSL.
- **keyfile**: Specifies the file with the secret key in the PEM format. - **keyfile**: Specifies the file with the secret key in the PEM format.
.. _patronictl_settings:
CTL CTL
--- ---
- **Optional**: - **Optional**:
+48
View File
@@ -3,6 +3,54 @@
Release notes Release notes
============= =============
Version 1.5.0
-------------
This version enables Patroni HA cluster to operate in a standby mode, introduces experimental support for running on Windows, and provides a new configuration parameter to register PostgreSQL service in Consul.
**New features**
- Standby cluster (Dmitry Dolgov)
One or more Patroni nodes can form a standby cluster that runs alongside the primary one (i.e. in another datacenter) and consists of standby nodes that replicate from the master in the primary cluster. All PostgreSQL nodes in the standby cluster are replicas; one of those replicas elects itself to replicate directly from the remote master, while the others replicate from it in a cascading manner. More detailed description of this feature and some configuration examples can be found at :ref:`here <standby_cluster>`.
- Register Services in Consul (Pavel Kirillov, Alexander Kukushkin)
If `register_service` parameter in the consul :ref:`configuration <consul_settings>` is enabled, the node will register a service with the name `scope` and the tag `master`, `replica` or `standby-leader`.
- Experimental Windows support (Pavel Golub)
From now on it is possible to run Patroni on Windows, although Windows support is brand-new and hasn't received as much real-world testing as its Linux counterpart. We welcome your feedback!
**Improvements in patronictl**
- Add patronictl -k/--insecure flag and support for restapi cert (Wilfried Roset)
In the past if the REST API was protected by the self-signed certificates `patronictl` would fail to verify them. There was no way to disable that verification. It is now possible to configure `patronictl` to skip the certificate verification altogether or provide CA and client certificates in the :ref:`ctl: <patronictl_settings>` section of configuration.
- Exclude members with nofailover tag from patronictl switchover/failover output (Alexander Anikin)
Previously, those members were incorrectly proposed as candidates when performing interactive switchover or failover via patronictl.
**Stability improvements**
- Avoid parsing non-key-value output lines of pg_controldata (Alexander Anikin)
Under certain circuimstances pg_controldata outputs lines without a colon character. That would trigger an error in Patroni code that parsed pg_controldata output, hiding the actual problem; often such lines are emitted in a warning shown by pg_controldata before the regular output, i.e. when the binary major version does not match the one of the PostgreSQL data directory.
- Add member name to the error message during the leader election (Jan Mussler)
During the leader election, Patroni connects to all known members of the cluster and requests their status. Such status is written to the Patroni log and includes the name of the member. Previously, if the member was not accessible, the error message did not indicate its name, containing only the URL.
- Immediately reserve the WAL position upon creation of the replication slot (Alexander Kukushkin)
Starting from 9.6, `pg_create_physical_replication_slot` function provides an additional boolean parameter `immediately_reserve`. When it is set to `false`, which is also the default, the slot doesn't reserve the WAL position until it receives the first client connection, potentially losing some segments required by the client in a time window between the slot creation and the intiial client connection.
- Fix bug in strict synchronous replication (Alexander Kukushkin)
When running with `synchronous_mode_strict: true`, in some cases Patroni puts `*` into the `synchronous_standby_names`, changing the sync state for most of the replication connections to `potential`. Previously, Patroni couldn't pick a synchronous candidate under such curcuimstances, as it only considered those with the state `async`.
Version 1.4.6 Version 1.4.6
------------- -------------
+2
View File
@@ -130,6 +130,8 @@ and
If all replica creation methods fail, Patroni will try again all methods in order during the next event loop cycle. If all replica creation methods fail, Patroni will try again all methods in order during the next event loop cycle.
.. _standby_cluster:
Standby cluster Standby cluster
--------------- ---------------
+8 -3
View File
@@ -125,7 +125,8 @@ class Patroni(object):
def setup_signal_handlers(self): def setup_signal_handlers(self):
self._received_sighup = False self._received_sighup = False
self._received_sigterm = False self._received_sigterm = False
signal.signal(signal.SIGHUP, self.sighup_handler) if os.name != 'nt':
signal.signal(signal.SIGHUP, self.sighup_handler)
signal.signal(signal.SIGTERM, self.sigterm_handler) signal.signal(signal.SIGTERM, self.sigterm_handler)
def shutdown(self): def shutdown(self):
@@ -154,6 +155,8 @@ def patroni_main():
def pg_ctl_start(args): def pg_ctl_start(args):
import subprocess import subprocess
if os.name != 'nt':
os.setsid()
postmaster = subprocess.Popen(args) postmaster = subprocess.Popen(args)
print(postmaster.pid) print(postmaster.pid)
@@ -197,11 +200,13 @@ def main():
os.kill(pid, signo) os.kill(pid, signo)
signal.signal(signal.SIGCHLD, sigchld_handler) signal.signal(signal.SIGCHLD, sigchld_handler)
signal.signal(signal.SIGHUP, passtochild) if os.name != 'nt':
signal.signal(signal.SIGHUP, passtochild)
signal.signal(signal.SIGQUIT, passtochild)
signal.signal(signal.SIGINT, passtochild) signal.signal(signal.SIGINT, passtochild)
signal.signal(signal.SIGUSR1, passtochild) signal.signal(signal.SIGUSR1, passtochild)
signal.signal(signal.SIGUSR2, passtochild) signal.signal(signal.SIGUSR2, passtochild)
signal.signal(signal.SIGQUIT, passtochild) signal.signal(signal.SIGABRT, passtochild)
signal.signal(signal.SIGTERM, passtochild) signal.signal(signal.SIGTERM, passtochild)
patroni = call_self(sys.argv[1:]) patroni = call_self(sys.argv[1:])
+8 -4
View File
@@ -1,11 +1,11 @@
import base64 import base64
import fcntl
import json import json
import logging import logging
import psycopg2 import psycopg2
import time import time
import dateutil.parser import dateutil.parser
import datetime import datetime
import os
from patroni.postgresql import PostgresConnectionException, PostgresException, Postgresql from patroni.postgresql import PostgresConnectionException, PostgresException, Postgresql
from patroni.utils import deep_compare, parse_bool, patch_config, Retry, \ from patroni.utils import deep_compare, parse_bool, patch_config, Retry, \
@@ -87,7 +87,9 @@ class RestApiHandler(BaseHTTPRequestHandler):
replica_status_code = 200 if not patroni.noloadbalance and response.get('role') == 'replica' else 503 replica_status_code = 200 if not patroni.noloadbalance and response.get('role') == 'replica' else 503
status_code = 503 status_code = 503
if 'master' in path: if patroni.config.is_standby_cluster and ('standby_leader' in path or 'standby-leader' in path):
status_code = 200 if patroni.ha.is_leader() else 503
elif 'master' in path or 'leader' in path or 'primary' in path:
status_code = 200 if patroni.ha.is_leader() else 503 status_code = 200 if patroni.ha.is_leader() else 503
elif 'replica' in path: elif 'replica' in path:
status_code = replica_status_code status_code = replica_status_code
@@ -479,8 +481,10 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
@staticmethod @staticmethod
def _set_fd_cloexec(fd): def _set_fd_cloexec(fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD) if os.name != 'nt':
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) import fcntl
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
def check_basic_auth_key(self, key): def check_basic_auth_key(self, key):
return self.__auth_key == key return self.__auth_key == key
+2 -1
View File
@@ -1,6 +1,7 @@
import json import json
import logging import logging
import os import os
import shutil
import six import six
import sys import sys
import tempfile import tempfile
@@ -128,7 +129,7 @@ class Config(object):
with os.fdopen(fd, 'w') as f: with os.fdopen(fd, 'w') as f:
fd = None fd = None
json.dump(self.dynamic_configuration, f) json.dump(self.dynamic_configuration, f)
tmpfile = os.rename(tmpfile, self._cache_file) tmpfile = shutil.move(tmpfile, self._cache_file)
self._cache_needs_saving = False self._cache_needs_saving = False
except Exception: except Exception:
logger.exception('Exception when saving file: %s', self._cache_file) logger.exception('Exception when saving file: %s', self._cache_file)
+2 -1
View File
@@ -6,6 +6,7 @@ import json
import logging import logging
import os import os
import pkgutil import pkgutil
import re
import six import six
import sys import sys
@@ -420,7 +421,7 @@ class AbstractDCS(object):
i.e.: `zookeeper` for zookeeper, `etcd` for etcd, etc... i.e.: `zookeeper` for zookeeper, `etcd` for etcd, etc...
""" """
self._name = config['name'] self._name = config['name']
self._base_path = os.path.join('/', config.get('namespace', '/service/').strip('/'), config['scope']) self._base_path = re.sub('/+', '/', '/'.join(['', config.get('namespace', 'service'), config['scope']]))
self._set_loop_wait(config.get('loop_wait', 10)) self._set_loop_wait(config.get('loop_wait', 10))
self._ctl = bool(config.get('patronictl', False)) self._ctl = bool(config.get('patronictl', False))
+3 -3
View File
@@ -288,7 +288,7 @@ class Consul(AbstractDCS):
nodes = {} nodes = {}
for node in results: for node in results:
node['Value'] = (node['Value'] or b'').decode('utf-8') node['Value'] = (node['Value'] or b'').decode('utf-8')
nodes[os.path.relpath(node['Key'], path)] = node nodes[os.path.relpath(node['Key'], path).replace('\\', '/')] = node
# get initialize flag # get initialize flag
initialize = nodes.get(self._INITIALIZE) initialize = nodes.get(self._INITIALIZE)
@@ -377,7 +377,7 @@ class Consul(AbstractDCS):
def _update_service(self, data): def _update_service(self, data):
service_name = self._service_name service_name = self._service_name
role = data['role'] role = data['role'].replace('_', '-')
state = data['state'] state = data['state']
api_parts = urlparse(data['api_url']) api_parts = urlparse(data['api_url'])
api_parts = api_parts._replace(path='/{0}'.format(role)) api_parts = api_parts._replace(path='/{0}'.format(role))
@@ -394,7 +394,7 @@ class Consul(AbstractDCS):
if state == 'stopped': if state == 'stopped':
return self.deregister_service(params['service_id']) return self.deregister_service(params['service_id'])
if role in ['master', 'replica']: if role in ['master', 'replica', 'standby-leader']:
if state != 'running': if state != 'running':
return return
return self.register_service(service_name, **params) return self.register_service(service_name, **params)
+1 -1
View File
@@ -448,7 +448,7 @@ class Etcd(AbstractDCS):
def _load_cluster(self): def _load_cluster(self):
try: try:
result = self.retry(self._client.read, self.client_path(''), recursive=True) result = self.retry(self._client.read, self.client_path(''), recursive=True)
nodes = {os.path.relpath(node.key, result.key): node for node in result.leaves} nodes = {os.path.relpath(node.key, result.key).replace('\\', '/'): node for node in result.leaves}
# get initialize flag # get initialize flag
initialize = nodes.get(self._INITIALIZE) initialize = nodes.get(self._INITIALIZE)
+9 -5
View File
@@ -640,7 +640,8 @@ class Postgresql(object):
return os.environ.copy() return os.environ.copy()
with open(self._pgpass, 'w') as f: with open(self._pgpass, 'w') as f:
os.fchmod(f.fileno(), 0o600) if os.name != 'nt':
os.fchmod(f.fileno(), 0o600)
f.write('{host}:{port}:*:{user}:{password}\n'.format(**record)) f.write('{host}:{port}:*:{user}:{password}\n'.format(**record))
env = os.environ.copy() env = os.environ.copy()
@@ -1124,9 +1125,10 @@ class Postgresql(object):
# therefore we need to make sure that hba_file is not overriden # therefore we need to make sure that hba_file is not overriden
# after changing superuser password we will "revert" all these "changes" # after changing superuser password we will "revert" all these "changes"
if self._running_custom_bootstrap or 'hba_file' not in self._server_parameters: if self._running_custom_bootstrap or 'hba_file' not in self._server_parameters:
f.write("hba_file = '{0}'\n".format(self._pg_hba_conf)) f.write("hba_file = '{0}'\n".format(self._pg_hba_conf.replace('\\', '\\\\')))
if 'ident_file' not in self._server_parameters: if 'ident_file' not in self._server_parameters:
f.write("ident_file = '{0}'\n".format(os.path.join(self._config_dir, 'pg_ident.conf'))) s = "ident_file = '{0}'\n".format(os.path.join(self._config_dir, 'pg_ident.conf').replace('\\', '\\\\'))
f.write(s)
def is_healthy(self): def is_healthy(self):
if not self.is_running(): if not self.is_running():
@@ -1221,8 +1223,10 @@ class Postgresql(object):
# Don't try to call pg_controldata during backup restore # Don't try to call pg_controldata during backup restore
if self._version_file_exists() and self.state != 'creating replica': if self._version_file_exists() and self.state != 'creating replica':
try: try:
data = subprocess.check_output([self._pgcommand('pg_controldata'), self._data_dir], env = {'LANG': 'C', 'LC_ALL': 'C', 'PATH': os.getenv('PATH')}
env={'LANG': 'C', 'LC_ALL': 'C', 'PATH': os.environ['PATH']}) if os.getenv('SYSTEMROOT') is not None:
env['SYSTEMROOT'] = os.getenv('SYSTEMROOT')
data = subprocess.check_output([self._pgcommand('pg_controldata'), self._data_dir], env=env)
if data: if data:
data = data.decode('utf-8').splitlines() data = data.decode('utf-8').splitlines()
# pg_controldata output depends on major verion. Some of parameters are prefixed by 'Current ' # pg_controldata output depends on major verion. Some of parameters are prefixed by 'Current '
+7 -6
View File
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
STOP_SIGNALS = { STOP_SIGNALS = {
'smart': signal.SIGTERM, 'smart': signal.SIGTERM,
'fast': signal.SIGINT, 'fast': signal.SIGINT,
'immediate': signal.SIGQUIT, 'immediate': signal.SIGQUIT if os.name != 'nt' else signal.SIGABRT,
} }
@@ -138,7 +138,8 @@ class PostmasterProcess(psutil.Process):
# of init process to take care about postmaster. # of init process to take care about postmaster.
# In order to make everything portable we can't use fork&exec approach here, so we will call # In order to make everything portable we can't use fork&exec approach here, so we will call
# ourselves and pass list of arguments which must be used to start postgres. # ourselves and pass list of arguments which must be used to start postgres.
env = {p: os.environ[p] for p in ('PATH', 'LD_LIBRARY_PATH', 'LC_ALL', 'LANG') if p in os.environ} # On Windows, in order to run a side-by-side assembly the specified env must include a valid SYSTEMROOT.
env = {p: os.environ[p] for p in ('PATH', 'LD_LIBRARY_PATH', 'LC_ALL', 'LANG', 'SYSTEMROOT') if p in os.environ}
try: try:
proc = PostmasterProcess._from_pidfile(data_dir) proc = PostmasterProcess._from_pidfile(data_dir)
if proc and not proc._is_postmaster_process(): if proc and not proc._is_postmaster_process():
@@ -153,10 +154,10 @@ class PostmasterProcess(psutil.Process):
env['PG_GRANDPARENT_PID'] = str(proc.pid) env['PG_GRANDPARENT_PID'] = str(proc.pid)
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
pass pass
cmdline = [pgcommand, '-D', data_dir, '--config-file={}'.format(conf)] + options
proc = call_self(['pg_ctl_start', pgcommand, '-D', data_dir, logger.debug("Starting postgres: %s", " ".join(cmdline))
'--config-file={}'.format(conf)] + options, close_fds=True, proc = call_self(['pg_ctl_start'] + cmdline, close_fds=(os.name != 'nt'),
preexec_fn=os.setsid, stdout=subprocess.PIPE, env=env) stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
pid = int(proc.stdout.readline().strip()) pid = int(proc.stdout.readline().strip())
proc.wait() proc.wait()
logger.info('postmaster pid=%s', pid) logger.info('postmaster pid=%s', pid)
+1 -1
View File
@@ -1 +1 @@
__version__ = '1.4.6' __version__ = '1.5.0'
+3
View File
@@ -52,6 +52,7 @@ CLASSIFIERS = [
'Operating System :: MacOS', 'Operating System :: MacOS',
'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: Linux',
'Operating System :: POSIX :: BSD :: FreeBSD', 'Operating System :: POSIX :: BSD :: FreeBSD',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
@@ -106,6 +107,8 @@ class PyTest(TestCommand):
silence = logging.WARNING silence = logging.WARNING
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=os.getenv('LOGLEVEL', silence)) logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=os.getenv('LOGLEVEL', silence))
params['args'] += ['-s' if logging.getLogger().getEffectiveLevel() < silence else '--capture=fd'] params['args'] += ['-s' if logging.getLogger().getEffectiveLevel() < silence else '--capture=fd']
if not os.getenv('SYSTEMROOT'):
os.environ['SYSTEMROOT'] = '/'
errno = pytest.main(**params) errno = pytest.main(**params)
sys.exit(errno) sys.exit(errno)
+2
View File
@@ -167,6 +167,8 @@ class TestRestApiHandler(unittest.TestCase):
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /master')) self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /master'))
with patch.object(RestApiServer, 'query', Mock(return_value=[('', 1, '', '', '', '', False, '')])): with patch.object(RestApiServer, 'query', Mock(return_value=[('', 1, '', '', '', '', False, '')])):
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /patroni')) self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /patroni'))
MockPatroni.config.is_standby_cluster = PropertyMock(return_value=True)
MockRestApiServer(RestApiHandler, 'GET /standby_leader')
def test_do_OPTIONS(self): def test_do_OPTIONS(self):
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'OPTIONS / HTTP/1.0')) self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'OPTIONS / HTTP/1.0'))
+1 -1
View File
@@ -76,7 +76,7 @@ class TestConfig(unittest.TestCase):
@patch('os.path.exists', Mock(return_value=True)) @patch('os.path.exists', Mock(return_value=True))
@patch('os.remove', Mock(side_effect=IOError)) @patch('os.remove', Mock(side_effect=IOError))
@patch('os.close', Mock(side_effect=IOError)) @patch('os.close', Mock(side_effect=IOError))
@patch('os.rename', Mock(return_value=None)) @patch('shutil.move', Mock(return_value=None))
@patch('json.dump', Mock()) @patch('json.dump', Mock())
def test_save_cache(self): def test_save_cache(self):
self.config.set_dynamic_configuration({'ttl': 30, 'postgresql': {'foo': 'bar'}}) self.config.set_dynamic_configuration({'ttl': 30, 'postgresql': {'foo': 'bar'}})
+16 -16
View File
@@ -196,7 +196,7 @@ class TestCtl(unittest.TestCase):
self.assertTrue('False' in str(rows)) self.assertTrue('False' in str(rows))
rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {})
self.assertEquals(rows, (None, None)) self.assertEqual(rows, (None, None))
with patch('test_postgresql.MockCursor.execute', Mock(side_effect=OperationalError('bla'))): with patch('test_postgresql.MockCursor.execute', Mock(side_effect=OperationalError('bla'))):
rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {})
@@ -364,20 +364,20 @@ class TestCtl(unittest.TestCase):
self.assertIsNone(get_any_member(get_cluster_initialized_without_leader(), role='master')) self.assertIsNone(get_any_member(get_cluster_initialized_without_leader(), role='master'))
m = get_any_member(get_cluster_initialized_with_leader(), role='master') m = get_any_member(get_cluster_initialized_with_leader(), role='master')
self.assertEquals(m.name, 'leader') self.assertEqual(m.name, 'leader')
def test_get_all_members(self): def test_get_all_members(self):
self.assertEquals(list(get_all_members(get_cluster_initialized_without_leader(), role='master')), []) self.assertEqual(list(get_all_members(get_cluster_initialized_without_leader(), role='master')), [])
r = list(get_all_members(get_cluster_initialized_with_leader(), role='master')) r = list(get_all_members(get_cluster_initialized_with_leader(), role='master'))
self.assertEquals(len(r), 1) self.assertEqual(len(r), 1)
self.assertEquals(r[0].name, 'leader') self.assertEqual(r[0].name, 'leader')
r = list(get_all_members(get_cluster_initialized_with_leader(), role='replica')) r = list(get_all_members(get_cluster_initialized_with_leader(), role='replica'))
self.assertEquals(len(r), 1) self.assertEqual(len(r), 1)
self.assertEquals(r[0].name, 'other') self.assertEqual(r[0].name, 'other')
self.assertEquals(len(list(get_all_members(get_cluster_initialized_without_leader(), role='replica'))), 2) self.assertEqual(len(list(get_all_members(get_cluster_initialized_without_leader(), role='replica'))), 2)
@patch('patroni.ctl.get_dcs') @patch('patroni.ctl.get_dcs')
def test_members(self, mock_get_dcs): def test_members(self, mock_get_dcs):
@@ -499,23 +499,23 @@ class TestCtl(unittest.TestCase):
after_editing, changed_config = apply_config_changes(before_editing, config, after_editing, changed_config = apply_config_changes(before_editing, config,
["postgresql.parameters.work_mem = 5MB", ["postgresql.parameters.work_mem = 5MB",
"ttl=15", "postgresql.use_pg_rewind=off", 'a.b=c']) "ttl=15", "postgresql.use_pg_rewind=off", 'a.b=c'])
self.assertEquals(changed_config, {"a": {"b": "c"}, "postgresql": {"parameters": {"work_mem": "5MB"}, self.assertEqual(changed_config, {"a": {"b": "c"}, "postgresql": {"parameters": {"work_mem": "5MB"},
"use_pg_rewind": False}, "ttl": 15}) "use_pg_rewind": False}, "ttl": 15})
# postgresql.parameters namespace is flattened # postgresql.parameters namespace is flattened
after_editing, changed_config = apply_config_changes(before_editing, config, after_editing, changed_config = apply_config_changes(before_editing, config,
["postgresql.parameters.work_mem.sub = x"]) ["postgresql.parameters.work_mem.sub = x"])
self.assertEquals(changed_config, {"postgresql": {"parameters": {"work_mem": "4MB", "work_mem.sub": "x"}, self.assertEqual(changed_config, {"postgresql": {"parameters": {"work_mem": "4MB", "work_mem.sub": "x"},
"use_pg_rewind": True}, "ttl": 30}) "use_pg_rewind": True}, "ttl": 30})
# Setting to null deletes # Setting to null deletes
after_editing, changed_config = apply_config_changes(before_editing, config, after_editing, changed_config = apply_config_changes(before_editing, config,
["postgresql.parameters.work_mem=null"]) ["postgresql.parameters.work_mem=null"])
self.assertEquals(changed_config, {"postgresql": {"use_pg_rewind": True}, "ttl": 30}) self.assertEqual(changed_config, {"postgresql": {"use_pg_rewind": True}, "ttl": 30})
after_editing, changed_config = apply_config_changes(before_editing, config, after_editing, changed_config = apply_config_changes(before_editing, config,
["postgresql.use_pg_rewind=null", ["postgresql.use_pg_rewind=null",
"postgresql.parameters.work_mem=null"]) "postgresql.parameters.work_mem=null"])
self.assertEquals(changed_config, {"ttl": 30}) self.assertEqual(changed_config, {"ttl": 30})
self.assertRaises(PatroniCtlException, apply_config_changes, before_editing, config, ['a']) self.assertRaises(PatroniCtlException, apply_config_changes, before_editing, config, ['a'])
@@ -572,5 +572,5 @@ class TestCtl(unittest.TestCase):
assert 'failed to get version' in result.output assert 'failed to get version' in result.output
def test_format_pg_version(self): def test_format_pg_version(self):
self.assertEquals(format_pg_version(100001), '10.1') self.assertEqual(format_pg_version(100001), '10.1')
self.assertEquals(format_pg_version(90605), '9.6.5') self.assertEqual(format_pg_version(90605), '9.6.5')
+3 -3
View File
@@ -215,8 +215,8 @@ class TestClient(unittest.TestCase):
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', 'GET') self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', 'GET')
def test_get_srv_record(self): def test_get_srv_record(self):
self.assertEquals(self.client.get_srv_record('_etcd-server._tcp.blabla'), []) self.assertEqual(self.client.get_srv_record('_etcd-server._tcp.blabla'), [])
self.assertEquals(self.client.get_srv_record('_etcd-server._tcp.exception'), []) self.assertEqual(self.client.get_srv_record('_etcd-server._tcp.exception'), [])
def test__get_machines_cache_from_srv(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')
@@ -259,7 +259,7 @@ class TestEtcd(unittest.TestCase):
'host': 'localhost:2379', 'scope': 'test', 'name': 'foo'}) 'host': 'localhost:2379', 'scope': 'test', 'name': 'foo'})
def test_base_path(self): def test_base_path(self):
self.assertEquals(self.etcd._base_path, '/patroni/test') self.assertEqual(self.etcd._base_path, '/patroni/test')
@patch('dns.resolver.query', dns_query) @patch('dns.resolver.query', dns_query)
def test_get_etcd_client(self): def test_get_etcd_client(self):
+110 -110
View File
@@ -209,7 +209,7 @@ class TestHa(unittest.TestCase):
def test_start_as_replica(self): def test_start_as_replica(self):
self.p.is_healthy = false self.p.is_healthy = false
self.assertEquals(self.ha.run_cycle(), 'starting as a secondary') self.assertEqual(self.ha.run_cycle(), 'starting as a secondary')
@patch('patroni.dcs.etcd.Etcd.initialize', return_value=True) @patch('patroni.dcs.etcd.Etcd.initialize', return_value=True)
def test_start_as_standby_leader(self, initialize): def test_start_as_standby_leader(self, initialize):
@@ -221,7 +221,7 @@ class TestHa(unittest.TestCase):
"port": 5432, "port": 5432,
"primary_slot_name": "", "primary_slot_name": "",
}} }}
self.assertEquals( self.assertEqual(
self.ha.run_cycle(), self.ha.run_cycle(),
'trying to bootstrap a new standby leader' 'trying to bootstrap a new standby leader'
) )
@@ -238,7 +238,7 @@ class TestHa(unittest.TestCase):
"port": 5432, "port": 5432,
"primary_slot_name": "", "primary_slot_name": "",
}} }}
self.assertEquals( self.assertEqual(
self.ha.run_cycle(), self.ha.run_cycle(),
"trying to bootstrap from replica 'test'" "trying to bootstrap from replica 'test'"
) )
@@ -253,14 +253,14 @@ class TestHa(unittest.TestCase):
"primary_slot_name": "", "primary_slot_name": "",
}} }}
self.ha._post_bootstrap_task = CriticalTask() self.ha._post_bootstrap_task = CriticalTask()
self.assertEquals(self.ha.bootstrap_standby_leader(), True) self.assertEqual(self.ha.bootstrap_standby_leader(), True)
def test_recover_replica_failed(self): def test_recover_replica_failed(self):
self.p.controldata = lambda: {'Database cluster state': 'in recovery', 'Database system identifier': SYSID} self.p.controldata = lambda: {'Database cluster state': 'in recovery', 'Database system identifier': SYSID}
self.p.is_running = false self.p.is_running = false
self.p.follow = false self.p.follow = false
self.assertEquals(self.ha.run_cycle(), 'starting as a secondary') self.assertEqual(self.ha.run_cycle(), 'starting as a secondary')
self.assertEquals(self.ha.run_cycle(), 'failed to start postgres') self.assertEqual(self.ha.run_cycle(), 'failed to start postgres')
def test_recover_former_master(self): def test_recover_former_master(self):
self.p.follow = false self.p.follow = false
@@ -269,19 +269,19 @@ class TestHa(unittest.TestCase):
self.p.set_role('master') self.p.set_role('master')
self.p.controldata = lambda: {'Database cluster state': 'shut down', 'Database system identifier': SYSID} self.p.controldata = lambda: {'Database cluster state': 'shut down', 'Database system identifier': SYSID}
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), 'starting as readonly because i had the session lock') self.assertEqual(self.ha.run_cycle(), 'starting as readonly because i had the session lock')
@patch.object(Postgresql, 'fix_cluster_state', Mock()) @patch.object(Postgresql, 'fix_cluster_state', Mock())
def test_crash_recovery(self): def test_crash_recovery(self):
self.p.is_running = false self.p.is_running = false
self.p.controldata = lambda: {'Database cluster state': 'in production', 'Database system identifier': SYSID} self.p.controldata = lambda: {'Database cluster state': 'in production', 'Database system identifier': SYSID}
self.assertEquals(self.ha.run_cycle(), 'doing crash recovery in a single user mode') self.assertEqual(self.ha.run_cycle(), 'doing crash recovery in a single user mode')
@patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True)) @patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True))
def test_recover_with_rewind(self): def test_recover_with_rewind(self):
self.p.is_running = false self.p.is_running = false
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), 'running pg_rewind from leader') self.assertEqual(self.ha.run_cycle(), 'running pg_rewind from leader')
@patch('sys.exit', return_value=1) @patch('sys.exit', return_value=1)
@patch('patroni.ha.Ha.sysid_valid', MagicMock(return_value=True)) @patch('patroni.ha.Ha.sysid_valid', MagicMock(return_value=True))
@@ -296,129 +296,129 @@ class TestHa(unittest.TestCase):
self.p.is_healthy = true self.p.is_healthy = true
self.ha.has_lock = true self.ha.has_lock = true
self.p.controldata = lambda: {'Database cluster state': 'in production', 'Database system identifier': SYSID} self.p.controldata = lambda: {'Database cluster state': 'in production', 'Database system identifier': SYSID}
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader because i had the session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
@patch('psycopg2.connect', psycopg2_connect) @patch('psycopg2.connect', psycopg2_connect)
def test_acquire_lock_as_master(self): def test_acquire_lock_as_master(self):
self.assertEquals(self.ha.run_cycle(), 'acquired session lock as a leader') self.assertEqual(self.ha.run_cycle(), 'acquired session lock as a leader')
def test_promoted_by_acquiring_lock(self): def test_promoted_by_acquiring_lock(self):
self.ha.is_healthiest_node = true self.ha.is_healthiest_node = true
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
def test_long_promote(self): def test_long_promote(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.has_lock = true self.ha.has_lock = true
self.p.is_leader = false self.p.is_leader = false
self.p.set_role('master') self.p.set_role('master')
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
def test_demote_after_failing_to_obtain_lock(self): def test_demote_after_failing_to_obtain_lock(self):
self.ha.acquire_lock = false self.ha.acquire_lock = false
self.assertEquals(self.ha.run_cycle(), 'demoted self after trying and failing to obtain lock') self.assertEqual(self.ha.run_cycle(), 'demoted self after trying and failing to obtain lock')
def test_follow_new_leader_after_failing_to_obtain_lock(self): def test_follow_new_leader_after_failing_to_obtain_lock(self):
self.ha.is_healthiest_node = true self.ha.is_healthiest_node = true
self.ha.acquire_lock = false self.ha.acquire_lock = false
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'following new leader after trying and failing to obtain lock') self.assertEqual(self.ha.run_cycle(), 'following new leader after trying and failing to obtain lock')
def test_demote_because_not_healthiest(self): def test_demote_because_not_healthiest(self):
self.ha.is_healthiest_node = false self.ha.is_healthiest_node = false
self.assertEquals(self.ha.run_cycle(), 'demoting self because i am not the healthiest node') self.assertEqual(self.ha.run_cycle(), 'demoting self because i am not the healthiest node')
def test_follow_new_leader_because_not_healthiest(self): def test_follow_new_leader_because_not_healthiest(self):
self.ha.is_healthiest_node = false self.ha.is_healthiest_node = false
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') self.assertEqual(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node')
def test_promote_because_have_lock(self): def test_promote_because_have_lock(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.has_lock = true self.ha.has_lock = true
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader because i had the session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
def test_promote_without_watchdog(self): def test_promote_without_watchdog(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.has_lock = true self.ha.has_lock = true
self.p.is_leader = true self.p.is_leader = true
with patch.object(Watchdog, 'activate', Mock(return_value=False)): with patch.object(Watchdog, 'activate', Mock(return_value=False)):
self.assertEquals(self.ha.run_cycle(), 'Demoting self because watchdog could not be activated') self.assertEqual(self.ha.run_cycle(), 'Demoting self because watchdog could not be activated')
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'Not promoting self because watchdog could not be activated') self.assertEqual(self.ha.run_cycle(), 'Not promoting self because watchdog could not be activated')
def test_leader_with_lock(self): def test_leader_with_lock(self):
self.ha.cluster = get_cluster_not_initialized_without_leader() self.ha.cluster = get_cluster_not_initialized_without_leader()
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.has_lock = true self.ha.has_lock = true
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
def test_demote_because_not_having_lock(self): def test_demote_because_not_having_lock(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
with patch.object(Watchdog, 'is_running', PropertyMock(return_value=True)): with patch.object(Watchdog, 'is_running', PropertyMock(return_value=True)):
self.assertEquals(self.ha.run_cycle(), 'demoting self because i do not have the lock and i was a leader') self.assertEqual(self.ha.run_cycle(), 'demoting self because i do not have the lock and i was a leader')
def test_demote_because_update_lock_failed(self): def test_demote_because_update_lock_failed(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.has_lock = true self.ha.has_lock = true
self.ha.update_lock = false self.ha.update_lock = false
self.assertEquals(self.ha.run_cycle(), 'demoted self because failed to update leader lock in DCS') self.assertEqual(self.ha.run_cycle(), 'demoted self because failed to update leader lock in DCS')
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'not promoting because failed to update leader lock in DCS') self.assertEqual(self.ha.run_cycle(), 'not promoting because failed to update leader lock in DCS')
def test_follow(self): def test_follow(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader') self.assertEqual(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader')
self.ha.patroni.replicatefrom = "foo" self.ha.patroni.replicatefrom = "foo"
self.assertEquals(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader') self.assertEqual(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader')
def test_follow_in_pause(self): def test_follow_in_pause(self):
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.is_paused = true self.ha.is_paused = true
self.assertEquals(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock') self.assertEqual(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock')
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'PAUSE: no action') self.assertEqual(self.ha.run_cycle(), 'PAUSE: no action')
@patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True)) @patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True))
def test_follow_triggers_rewind(self): def test_follow_triggers_rewind(self):
self.p.is_leader = false self.p.is_leader = false
self.p.trigger_check_diverged_lsn() self.p.trigger_check_diverged_lsn()
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), 'running pg_rewind from leader') self.assertEqual(self.ha.run_cycle(), 'running pg_rewind from leader')
def test_no_etcd_connection_master_demote(self): def test_no_etcd_connection_master_demote(self):
self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly')) self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly'))
self.assertEquals(self.ha.run_cycle(), 'demoted self because DCS is not accessible and i was a leader') self.assertEqual(self.ha.run_cycle(), 'demoted self because DCS is not accessible and i was a leader')
@patch('time.sleep', Mock()) @patch('time.sleep', Mock())
def test_bootstrap_from_another_member(self): def test_bootstrap_from_another_member(self):
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.bootstrap(), 'trying to bootstrap from replica \'other\'') self.assertEqual(self.ha.bootstrap(), 'trying to bootstrap from replica \'other\'')
def test_bootstrap_waiting_for_leader(self): def test_bootstrap_waiting_for_leader(self):
self.ha.cluster = get_cluster_initialized_without_leader() self.ha.cluster = get_cluster_initialized_without_leader()
self.assertEquals(self.ha.bootstrap(), 'waiting for leader to bootstrap') self.assertEqual(self.ha.bootstrap(), 'waiting for leader to bootstrap')
def test_bootstrap_without_leader(self): def test_bootstrap_without_leader(self):
self.ha.cluster = get_cluster_initialized_without_leader() self.ha.cluster = get_cluster_initialized_without_leader()
self.p.can_create_replica_without_replication_connection = MagicMock(return_value=True) self.p.can_create_replica_without_replication_connection = MagicMock(return_value=True)
self.assertEquals(self.ha.bootstrap(), 'trying to bootstrap (without leader)') self.assertEqual(self.ha.bootstrap(), 'trying to bootstrap (without leader)')
def test_bootstrap_initialize_lock_failed(self): def test_bootstrap_initialize_lock_failed(self):
self.ha.cluster = get_cluster_not_initialized_without_leader() self.ha.cluster = get_cluster_not_initialized_without_leader()
self.assertEquals(self.ha.bootstrap(), 'failed to acquire initialize lock') self.assertEqual(self.ha.bootstrap(), 'failed to acquire initialize lock')
def test_bootstrap_initialized_new_cluster(self): def test_bootstrap_initialized_new_cluster(self):
self.ha.cluster = get_cluster_not_initialized_without_leader() self.ha.cluster = get_cluster_not_initialized_without_leader()
self.e.initialize = true self.e.initialize = true
self.assertEquals(self.ha.bootstrap(), 'trying to bootstrap a new cluster') self.assertEqual(self.ha.bootstrap(), 'trying to bootstrap a new cluster')
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'waiting for end of recovery after bootstrap') self.assertEqual(self.ha.run_cycle(), 'waiting for end of recovery after bootstrap')
self.p.is_leader = true self.p.is_leader = true
self.assertEquals(self.ha.run_cycle(), 'running post_bootstrap') self.assertEqual(self.ha.run_cycle(), 'running post_bootstrap')
self.assertEquals(self.ha.run_cycle(), 'initialized a new cluster') self.assertEqual(self.ha.run_cycle(), 'initialized a new cluster')
def test_bootstrap_release_initialize_key_on_failure(self): def test_bootstrap_release_initialize_key_on_failure(self):
self.ha.cluster = get_cluster_not_initialized_without_leader() self.ha.cluster = get_cluster_not_initialized_without_leader()
@@ -434,7 +434,7 @@ class TestHa(unittest.TestCase):
self.p.is_running.return_value = MockPostmaster() self.p.is_running.return_value = MockPostmaster()
self.p.is_leader = true self.p.is_leader = true
with patch.object(Watchdog, 'activate', Mock(return_value=False)): with patch.object(Watchdog, 'activate', Mock(return_value=False)):
self.assertEquals(self.ha.post_bootstrap(), 'running post_bootstrap') self.assertEqual(self.ha.post_bootstrap(), 'running post_bootstrap')
self.assertRaises(PatroniException, self.ha.post_bootstrap) self.assertRaises(PatroniException, self.ha.post_bootstrap)
@patch('psycopg2.connect', psycopg2_connect) @patch('psycopg2.connect', psycopg2_connect)
@@ -451,35 +451,35 @@ class TestHa(unittest.TestCase):
@patch('time.sleep', Mock()) @patch('time.sleep', Mock())
def test_restart(self): def test_restart(self):
self.assertEquals(self.ha.restart({}), (True, 'restarted successfully')) self.assertEqual(self.ha.restart({}), (True, 'restarted successfully'))
self.p.restart = Mock(return_value=None) self.p.restart = Mock(return_value=None)
self.assertEquals(self.ha.restart({}), (False, 'postgres is still starting')) self.assertEqual(self.ha.restart({}), (False, 'postgres is still starting'))
self.p.restart = false self.p.restart = false
self.assertEquals(self.ha.restart({}), (False, 'restart failed')) self.assertEqual(self.ha.restart({}), (False, 'restart failed'))
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.ha.reinitialize() self.ha.reinitialize()
self.assertEquals(self.ha.restart({}), (False, 'reinitialize already in progress')) self.assertEqual(self.ha.restart({}), (False, 'reinitialize already in progress'))
with patch.object(self.ha, "restart_matches", return_value=False): with patch.object(self.ha, "restart_matches", return_value=False):
self.assertEquals(self.ha.restart({'foo': 'bar'}), (False, "restart conditions are not satisfied")) self.assertEqual(self.ha.restart({'foo': 'bar'}), (False, "restart conditions are not satisfied"))
@patch('os.kill', Mock()) @patch('os.kill', Mock())
def test_restart_in_progress(self): def test_restart_in_progress(self):
with patch('patroni.async_executor.AsyncExecutor.busy', PropertyMock(return_value=True)): with patch('patroni.async_executor.AsyncExecutor.busy', PropertyMock(return_value=True)):
self.ha.restart({}, run_async=True) self.ha.restart({}, run_async=True)
self.assertTrue(self.ha.restart_scheduled()) self.assertTrue(self.ha.restart_scheduled())
self.assertEquals(self.ha.run_cycle(), 'restart in progress') self.assertEqual(self.ha.run_cycle(), 'restart in progress')
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), 'restart in progress') self.assertEqual(self.ha.run_cycle(), 'restart in progress')
self.ha.has_lock = true self.ha.has_lock = true
self.assertEquals(self.ha.run_cycle(), 'updated leader lock during restart') self.assertEqual(self.ha.run_cycle(), 'updated leader lock during restart')
self.ha.update_lock = false self.ha.update_lock = false
self.p.set_role('master') self.p.set_role('master')
with patch('patroni.async_executor.CriticalTask.cancel', Mock(return_value=False)): with patch('patroni.async_executor.CriticalTask.cancel', Mock(return_value=False)):
with patch('patroni.postgresql.Postgresql.terminate_starting_postmaster') as mock_terminate: with patch('patroni.postgresql.Postgresql.terminate_starting_postmaster') as mock_terminate:
self.assertEquals(self.ha.run_cycle(), 'lost leader lock during restart') self.assertEqual(self.ha.run_cycle(), 'lost leader lock during restart')
mock_terminate.assert_called() mock_terminate.assert_called()
@patch('requests.get', requests_get) @patch('requests.get', requests_get)
@@ -487,25 +487,25 @@ class TestHa(unittest.TestCase):
self.ha.fetch_node_status = get_node_status() self.ha.fetch_node_status = get_node_status()
self.ha.has_lock = true self.ha.has_lock = true
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', '', None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', '', None))
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', self.p.name, None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', self.p.name, None))
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', 'blabla', None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', 'blabla', None))
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
f = Failover(0, self.p.name, '', None) f = Failover(0, self.p.name, '', None)
self.ha.cluster = get_cluster_initialized_with_leader(f) self.ha.cluster = get_cluster_initialized_with_leader(f)
self.assertEquals(self.ha.run_cycle(), 'manual failover: demoting myself') self.assertEqual(self.ha.run_cycle(), 'manual failover: demoting myself')
self.p.rewind_needed_and_possible = true self.p.rewind_needed_and_possible = true
self.assertEquals(self.ha.run_cycle(), 'manual failover: demoting myself') self.assertEqual(self.ha.run_cycle(), 'manual failover: demoting myself')
self.ha.fetch_node_status = get_node_status(nofailover=True) self.ha.fetch_node_status = get_node_status(nofailover=True)
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
self.ha.fetch_node_status = get_node_status(watchdog_failed=True) self.ha.fetch_node_status = get_node_status(watchdog_failed=True)
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
self.ha.fetch_node_status = get_node_status(wal_position=1) self.ha.fetch_node_status = get_node_status(wal_position=1)
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
# manual failover from the previous leader to us won't happen if we hold the nofailover flag # manual failover from the previous leader to us won't happen if we hold the nofailover flag
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, None))
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
# Failover scheduled time must include timezone # Failover scheduled time must include timezone
scheduled = datetime.datetime.now() scheduled = datetime.datetime.now()
@@ -514,19 +514,19 @@ class TestHa(unittest.TestCase):
scheduled = datetime.datetime.utcnow().replace(tzinfo=tzutc) scheduled = datetime.datetime.utcnow().replace(tzinfo=tzutc)
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled))
self.assertEquals('no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('no action. i am the leader with the lock', self.ha.run_cycle())
scheduled = scheduled + datetime.timedelta(seconds=30) scheduled = scheduled + datetime.timedelta(seconds=30)
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled))
self.assertEquals('no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('no action. i am the leader with the lock', self.ha.run_cycle())
scheduled = scheduled + datetime.timedelta(seconds=-600) scheduled = scheduled + datetime.timedelta(seconds=-600)
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled))
self.assertEquals('no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('no action. i am the leader with the lock', self.ha.run_cycle())
scheduled = None scheduled = None
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled))
self.assertEquals('no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('no action. i am the leader with the lock', self.ha.run_cycle())
@patch('requests.get', requests_get) @patch('requests.get', requests_get)
def test_manual_failover_from_leader_in_pause(self): def test_manual_failover_from_leader_in_pause(self):
@@ -534,9 +534,9 @@ class TestHa(unittest.TestCase):
self.ha.is_paused = true self.ha.is_paused = true
scheduled = datetime.datetime.now() scheduled = datetime.datetime.now()
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', self.p.name, scheduled))
self.assertEquals('PAUSE: no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('PAUSE: no action. i am the leader with the lock', self.ha.run_cycle())
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, '', None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, '', None))
self.assertEquals('PAUSE: no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('PAUSE: no action. i am the leader with the lock', self.ha.run_cycle())
@patch('requests.get', requests_get) @patch('requests.get', requests_get)
def test_manual_failover_from_leader_in_synchronous_mode(self): def test_manual_failover_from_leader_in_synchronous_mode(self):
@@ -546,48 +546,48 @@ class TestHa(unittest.TestCase):
self.ha.is_failover_possible = false self.ha.is_failover_possible = false
self.ha.process_sync_replication = Mock() self.ha.process_sync_replication = Mock()
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, 'a', None), (self.p.name, None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, 'a', None), (self.p.name, None))
self.assertEquals('no action. i am the leader with the lock', self.ha.run_cycle()) self.assertEqual('no action. i am the leader with the lock', self.ha.run_cycle())
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, 'a', None), (self.p.name, 'a')) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, self.p.name, 'a', None), (self.p.name, 'a'))
self.ha.is_failover_possible = true self.ha.is_failover_possible = true
self.assertEquals('manual failover: demoting myself', self.ha.run_cycle()) self.assertEqual('manual failover: demoting myself', self.ha.run_cycle())
@patch('requests.get', requests_get) @patch('requests.get', requests_get)
def test_manual_failover_process_no_leader(self): def test_manual_failover_process_no_leader(self):
self.p.is_leader = false self.p.is_leader = false
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', self.p.name, None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', self.p.name, None))
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'leader', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'leader', None))
self.p.set_role('replica') self.p.set_role('replica')
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
self.ha.fetch_node_status = get_node_status() # accessible, in_recovery self.ha.fetch_node_status = get_node_status() # accessible, in_recovery
self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') self.assertEqual(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node')
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, self.p.name, '', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, self.p.name, '', None))
self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') self.assertEqual(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node')
self.ha.fetch_node_status = get_node_status(reachable=False) # inaccessible, in_recovery self.ha.fetch_node_status = get_node_status(reachable=False) # inaccessible, in_recovery
self.p.set_role('replica') self.p.set_role('replica')
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
# set failover flag to True for all members of the cluster # set failover flag to True for all members of the cluster
# this should elect the current member, as we are not going to call the API for it. # this should elect the current member, as we are not going to call the API for it.
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'other', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'other', None))
self.ha.fetch_node_status = get_node_status(nofailover=True) # accessible, in_recovery self.ha.fetch_node_status = get_node_status(nofailover=True) # accessible, in_recovery
self.p.set_role('replica') self.p.set_role('replica')
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
# same as previous, but set the current member to nofailover. In no case it should be elected as a leader # same as previous, but set the current member to nofailover. In no case it should be elected as a leader
self.ha.patroni.nofailover = True self.ha.patroni.nofailover = True
self.assertEquals(self.ha.run_cycle(), 'following a different leader because I am not allowed to promote') self.assertEqual(self.ha.run_cycle(), 'following a different leader because I am not allowed to promote')
def test_manual_failover_process_no_leader_in_pause(self): def test_manual_failover_process_no_leader_in_pause(self):
self.ha.is_paused = true self.ha.is_paused = true
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'other', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'other', None))
self.assertEquals(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock') self.assertEqual(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock')
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', '', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', '', None))
self.assertEquals(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock') self.assertEqual(self.ha.run_cycle(), 'PAUSE: continue to run as master without lock')
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', 'blabla', None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', 'blabla', None))
self.assertEquals('PAUSE: acquired session lock as a leader', self.ha.run_cycle()) self.assertEqual('PAUSE: acquired session lock as a leader', self.ha.run_cycle())
self.p.is_leader = false self.p.is_leader = false
self.p.set_role('replica') self.p.set_role('replica')
self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', self.p.name, None)) self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, 'leader', self.p.name, None))
self.assertEquals(self.ha.run_cycle(), 'PAUSE: promoted self to leader by acquiring session lock') self.assertEqual(self.ha.run_cycle(), 'PAUSE: promoted self to leader by acquiring session lock')
def test_is_healthiest_node(self): def test_is_healthiest_node(self):
self.ha.state_handler.is_leader = false self.ha.state_handler.is_leader = false
@@ -668,7 +668,7 @@ class TestHa(unittest.TestCase):
def test_scheduled_restart(self): def test_scheduled_restart(self):
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
with patch.object(self.ha, "evaluate_scheduled_restart", Mock(return_value="restart scheduled")): with patch.object(self.ha, "evaluate_scheduled_restart", Mock(return_value="restart scheduled")):
self.assertEquals(self.ha.run_cycle(), "restart scheduled") self.assertEqual(self.ha.run_cycle(), "restart scheduled")
def test_restart_matches(self): def test_restart_matches(self):
self.p._role = 'replica' self.p._role = 'replica'
@@ -685,9 +685,9 @@ class TestHa(unittest.TestCase):
self.ha.is_paused = true self.ha.is_paused = true
self.p.name = 'leader' self.p.name = 'leader'
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), 'PAUSE: removed leader lock because postgres is not running as master') self.assertEqual(self.ha.run_cycle(), 'PAUSE: removed leader lock because postgres is not running as master')
self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', self.p.name, None)) self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, '', self.p.name, None))
self.assertEquals(self.ha.run_cycle(), 'PAUSE: waiting to become master after promote...') self.assertEqual(self.ha.run_cycle(), 'PAUSE: waiting to become master after promote...')
def test_process_healthy_standby_cluster_as_standby_leader(self): def test_process_healthy_standby_cluster_as_standby_leader(self):
self.p.is_leader = false self.p.is_leader = false
@@ -699,7 +699,7 @@ class TestHa(unittest.TestCase):
}} }}
self.ha.cluster = get_standby_cluster_initialized_with_only_leader() self.ha.cluster = get_standby_cluster_initialized_with_only_leader()
msg = 'no action. i am the standby leader with the lock' msg = 'no action. i am the standby leader with the lock'
self.assertEquals(self.ha.run_cycle(), msg) self.assertEqual(self.ha.run_cycle(), msg)
def test_process_healthy_standby_cluster_as_cascade_replica(self): def test_process_healthy_standby_cluster_as_cascade_replica(self):
self.p.is_leader = false self.p.is_leader = false
@@ -711,7 +711,7 @@ class TestHa(unittest.TestCase):
}} }}
self.ha.cluster = get_standby_cluster_initialized_with_only_leader() self.ha.cluster = get_standby_cluster_initialized_with_only_leader()
msg = 'no action. i am a secondary and i am following a leader' msg = 'no action. i am a secondary and i am following a leader'
self.assertEquals(self.ha.run_cycle(), msg) self.assertEqual(self.ha.run_cycle(), msg)
@patch('patroni.dcs.etcd.Etcd.initialize', return_value=True) @patch('patroni.dcs.etcd.Etcd.initialize', return_value=True)
def test_process_unhealthy_standby_cluster_as_standby_leader(self, initialize): def test_process_unhealthy_standby_cluster_as_standby_leader(self, initialize):
@@ -727,7 +727,7 @@ class TestHa(unittest.TestCase):
self.ha.sysid_valid = true self.ha.sysid_valid = true
self.p._sysid = True self.p._sysid = True
msg = 'promoted self to a standby leader because i had the session lock' msg = 'promoted self to a standby leader because i had the session lock'
self.assertEquals(self.ha.run_cycle(), msg) self.assertEqual(self.ha.run_cycle(), msg)
@patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True)) @patch.object(Postgresql, 'rewind_needed_and_possible', Mock(return_value=True))
@patch('patroni.dcs.etcd.Etcd.initialize', return_value=True) @patch('patroni.dcs.etcd.Etcd.initialize', return_value=True)
@@ -742,27 +742,27 @@ class TestHa(unittest.TestCase):
self.ha.cluster = get_standby_cluster_initialized_with_only_leader() self.ha.cluster = get_standby_cluster_initialized_with_only_leader()
self.ha.is_unlocked = true self.ha.is_unlocked = true
msg = 'running pg_rewind from leader' msg = 'running pg_rewind from leader'
self.assertEquals(self.ha.run_cycle(), msg) self.assertEqual(self.ha.run_cycle(), msg)
def test_failed_to_update_lock_in_pause(self): def test_failed_to_update_lock_in_pause(self):
self.ha.update_lock = false self.ha.update_lock = false
self.ha.is_paused = true self.ha.is_paused = true
self.p.name = 'leader' self.p.name = 'leader'
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.assertEquals(self.ha.run_cycle(), self.assertEqual(self.ha.run_cycle(),
'PAUSE: continue to run as master after failing to update leader lock in DCS') 'PAUSE: continue to run as master after failing to update leader lock in DCS')
def test_postgres_unhealthy_in_pause(self): def test_postgres_unhealthy_in_pause(self):
self.ha.is_paused = true self.ha.is_paused = true
self.p.is_healthy = false self.p.is_healthy = false
self.assertEquals(self.ha.run_cycle(), 'PAUSE: postgres is not running') self.assertEqual(self.ha.run_cycle(), 'PAUSE: postgres is not running')
self.ha.has_lock = true self.ha.has_lock = true
self.assertEquals(self.ha.run_cycle(), 'PAUSE: removed leader lock because postgres is not running') self.assertEqual(self.ha.run_cycle(), 'PAUSE: removed leader lock because postgres is not running')
def test_no_etcd_connection_in_pause(self): def test_no_etcd_connection_in_pause(self):
self.ha.is_paused = true self.ha.is_paused = true
self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly')) self.ha.load_cluster_from_dcs = Mock(side_effect=DCSError('Etcd is not responding properly'))
self.assertEquals(self.ha.run_cycle(), 'PAUSE: DCS is not accessible') self.assertEqual(self.ha.run_cycle(), 'PAUSE: DCS is not accessible')
@patch('patroni.ha.Ha.update_lock', return_value=True) @patch('patroni.ha.Ha.update_lock', return_value=True)
@patch('patroni.ha.Ha.demote') @patch('patroni.ha.Ha.demote')
@@ -778,26 +778,26 @@ class TestHa(unittest.TestCase):
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.p.check_for_startup = true self.p.check_for_startup = true
self.p.time_in_state = lambda: 30 self.p.time_in_state = lambda: 30
self.assertEquals(self.ha.run_cycle(), 'PostgreSQL is still starting up, 270 seconds until timeout') self.assertEqual(self.ha.run_cycle(), 'PostgreSQL is still starting up, 270 seconds until timeout')
check_calls([(update_lock, True), (demote, False)]) check_calls([(update_lock, True), (demote, False)])
self.p.time_in_state = lambda: 350 self.p.time_in_state = lambda: 350
self.ha.fetch_node_status = get_node_status(reachable=False) # inaccessible, in_recovery self.ha.fetch_node_status = get_node_status(reachable=False) # inaccessible, in_recovery
self.assertEquals(self.ha.run_cycle(), self.assertEqual(self.ha.run_cycle(),
'master start has timed out, but continuing to wait because failover is not possible') 'master start has timed out, but continuing to wait because failover is not possible')
check_calls([(update_lock, True), (demote, False)]) check_calls([(update_lock, True), (demote, False)])
self.ha.fetch_node_status = get_node_status() # accessible, in_recovery self.ha.fetch_node_status = get_node_status() # accessible, in_recovery
self.assertEquals(self.ha.run_cycle(), 'stopped PostgreSQL because of startup timeout') self.assertEqual(self.ha.run_cycle(), 'stopped PostgreSQL because of startup timeout')
check_calls([(update_lock, True), (demote, True)]) check_calls([(update_lock, True), (demote, True)])
update_lock.return_value = False update_lock.return_value = False
self.assertEquals(self.ha.run_cycle(), 'stopped PostgreSQL while starting up because leader key was lost') self.assertEqual(self.ha.run_cycle(), 'stopped PostgreSQL while starting up because leader key was lost')
check_calls([(update_lock, True), (demote, True)]) check_calls([(update_lock, True), (demote, True)])
self.ha.has_lock = false self.ha.has_lock = false
self.p.is_leader = false self.p.is_leader = false
self.assertEquals(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader') self.assertEqual(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader')
check_calls([(update_lock, False), (demote, False)]) check_calls([(update_lock, False), (demote, False)])
def test_manual_failover_while_starting(self): def test_manual_failover_while_starting(self):
@@ -806,7 +806,7 @@ class TestHa(unittest.TestCase):
f = Failover(0, self.p.name, '', None) f = Failover(0, self.p.name, '', None)
self.ha.cluster = get_cluster_initialized_with_leader(f) self.ha.cluster = get_cluster_initialized_with_leader(f)
self.ha.fetch_node_status = get_node_status() # accessible, in_recovery self.ha.fetch_node_status = get_node_status() # accessible, in_recovery
self.assertEquals(self.ha.run_cycle(), 'manual failover: demoting myself') self.assertEqual(self.ha.run_cycle(), 'manual failover: demoting myself')
@patch('patroni.ha.Ha.demote') @patch('patroni.ha.Ha.demote')
def test_failover_immediately_on_zero_master_start_timeout(self, demote): def test_failover_immediately_on_zero_master_start_timeout(self, demote):
@@ -817,7 +817,7 @@ class TestHa(unittest.TestCase):
self.ha.has_lock = true self.ha.has_lock = true
self.ha.update_lock = true self.ha.update_lock = true
self.ha.fetch_node_status = get_node_status() # accessible, in_recovery self.ha.fetch_node_status = get_node_status() # accessible, in_recovery
self.assertEquals(self.ha.run_cycle(), 'stopped PostgreSQL to fail over after a crash') self.assertEqual(self.ha.run_cycle(), 'stopped PostgreSQL to fail over after a crash')
demote.assert_called_once() demote.assert_called_once()
@patch('patroni.postgresql.Postgresql.follow') @patch('patroni.postgresql.Postgresql.follow')
@@ -879,18 +879,18 @@ class TestHa(unittest.TestCase):
self.p.pick_synchronous_standby = Mock(return_value=('other2', True)) self.p.pick_synchronous_standby = Mock(return_value=('other2', True))
self.ha.run_cycle() self.ha.run_cycle()
self.ha.dcs.get_cluster.assert_called_once() self.ha.dcs.get_cluster.assert_called_once()
self.assertEquals(self.ha.dcs.write_sync_state.call_count, 2) self.assertEqual(self.ha.dcs.write_sync_state.call_count, 2)
# Test updating sync standby key failed due to race # Test updating sync standby key failed due to race
self.ha.dcs.write_sync_state = Mock(side_effect=[True, False]) self.ha.dcs.write_sync_state = Mock(side_effect=[True, False])
self.ha.run_cycle() self.ha.run_cycle()
self.assertEquals(self.ha.dcs.write_sync_state.call_count, 2) self.assertEqual(self.ha.dcs.write_sync_state.call_count, 2)
# Test changing sync standby failed due to race # Test changing sync standby failed due to race
self.ha.dcs.write_sync_state = Mock(return_value=True) self.ha.dcs.write_sync_state = Mock(return_value=True)
self.ha.dcs.get_cluster = Mock(return_value=get_cluster_initialized_with_leader(sync=('somebodyelse', None))) self.ha.dcs.get_cluster = Mock(return_value=get_cluster_initialized_with_leader(sync=('somebodyelse', None)))
self.ha.run_cycle() self.ha.run_cycle()
self.assertEquals(self.ha.dcs.write_sync_state.call_count, 1) self.assertEqual(self.ha.dcs.write_sync_state.call_count, 1)
# Test sync set to '*' when synchronous_mode_strict is enabled # Test sync set to '*' when synchronous_mode_strict is enabled
mock_set_sync.reset_mock() mock_set_sync.reset_mock()
@@ -911,7 +911,7 @@ class TestHa(unittest.TestCase):
self.ha.cluster = get_cluster_initialized_with_leader(sync=('other', None)) self.ha.cluster = get_cluster_initialized_with_leader(sync=('other', None))
# When we just became master nobody is sync # When we just became master nobody is sync
self.assertEquals(self.ha.enforce_master_role('msg', 'promote msg'), 'promote msg') self.assertEqual(self.ha.enforce_master_role('msg', 'promote msg'), 'promote msg')
mock_set_sync.assert_called_once_with(None) mock_set_sync.assert_called_once_with(None)
mock_write_sync.assert_called_once_with('leader', None, index=0) mock_write_sync.assert_called_once_with('leader', None, index=0)
@@ -939,7 +939,7 @@ class TestHa(unittest.TestCase):
self.ha.run_cycle() self.ha.run_cycle()
mock_acquire.assert_not_called() mock_acquire.assert_not_called()
mock_follow.assert_called_once() mock_follow.assert_called_once()
self.assertEquals(mock_follow.call_args[0][0], None) self.assertEqual(mock_follow.call_args[0][0], None)
mock_write_sync.assert_not_called() mock_write_sync.assert_not_called()
mock_follow.reset_mock() mock_follow.reset_mock()
@@ -990,15 +990,15 @@ class TestHa(unittest.TestCase):
def test_effective_tags(self): def test_effective_tags(self):
self.ha._disable_sync = True self.ha._disable_sync = True
self.assertEquals(self.ha.get_effective_tags(), {'foo': 'bar', 'nosync': True}) self.assertEqual(self.ha.get_effective_tags(), {'foo': 'bar', 'nosync': True})
self.ha._disable_sync = False self.ha._disable_sync = False
self.assertEquals(self.ha.get_effective_tags(), {'foo': 'bar'}) self.assertEqual(self.ha.get_effective_tags(), {'foo': 'bar'})
def test_restore_cluster_config(self): def test_restore_cluster_config(self):
self.ha.cluster.config.data.clear() self.ha.cluster.config.data.clear()
self.ha.has_lock = true self.ha.has_lock = true
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
def test_watch(self): def test_watch(self):
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
@@ -1017,19 +1017,19 @@ class TestHa(unittest.TestCase):
self.ha.cluster = get_cluster_initialized_with_leader() self.ha.cluster = get_cluster_initialized_with_leader()
self.ha.has_lock = true self.ha.has_lock = true
self.p.data_directory_empty = true self.p.data_directory_empty = true
self.assertEquals(self.ha.run_cycle(), 'released leader key voluntarily as data dir empty and currently leader') self.assertEqual(self.ha.run_cycle(), 'released leader key voluntarily as data dir empty and currently leader')
self.assertEquals(self.p.role, 'uninitialized') self.assertEqual(self.p.role, 'uninitialized')
# as has_lock is mocked out, we need to fake the leader key release # as has_lock is mocked out, we need to fake the leader key release
self.ha.has_lock = false self.ha.has_lock = false
# will not say bootstrap from leader as replica can't self elect # will not say bootstrap from leader as replica can't self elect
self.assertEquals(self.ha.run_cycle(), "trying to bootstrap from replica 'other'") self.assertEqual(self.ha.run_cycle(), "trying to bootstrap from replica 'other'")
def test_update_cluster_history(self): def test_update_cluster_history(self):
self.p.get_master_timeline = Mock(return_value=1) self.p.get_master_timeline = Mock(return_value=1)
self.ha.has_lock = true self.ha.has_lock = true
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
@patch('sys.exit', return_value=1) @patch('sys.exit', return_value=1)
def test_abort_join(self, exit_mock): def test_abort_join(self, exit_mock):
@@ -1042,6 +1042,6 @@ class TestHa(unittest.TestCase):
self.ha.has_lock = true self.ha.has_lock = true
self.ha.cluster.is_unlocked = false self.ha.cluster.is_unlocked = false
self.ha.is_paused = true self.ha.is_paused = true
self.assertEquals(self.ha.run_cycle(), 'PAUSE: no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'PAUSE: no action. i am the leader with the lock')
self.ha.is_paused = false self.ha.is_paused = false
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') self.assertEqual(self.ha.run_cycle(), 'no action. i am the leader with the lock')
+1 -1
View File
@@ -73,7 +73,7 @@ class TestPatroni(unittest.TestCase):
mock_getpid.return_value = 2 mock_getpid.return_value = 2
_main() _main()
with patch('sys.frozen', Mock(return_value=True), create=True): with patch('sys.frozen', Mock(return_value=True), create=True), patch('os.setsid', Mock()):
sys.argv = ['/patroni', 'pg_ctl_start', 'postgres', '-D', '/data', '--max_connections=100'] sys.argv = ['/patroni', 'pg_ctl_start', 'postgres', '-D', '/data', '--max_connections=100']
_main() _main()
+57 -56
View File
@@ -308,7 +308,7 @@ class TestPostgresql(unittest.TestCase):
def test_restart(self): def test_restart(self):
self.p.start = Mock(return_value=False) self.p.start = Mock(return_value=False)
self.assertFalse(self.p.restart()) self.assertFalse(self.p.restart())
self.assertEquals(self.p.state, 'restart failed (restarting)') self.assertEqual(self.p.state, 'restart failed (restarting)')
@patch.object(builtins, 'open', MagicMock()) @patch.object(builtins, 'open', MagicMock())
def test_write_pgpass(self): def test_write_pgpass(self):
@@ -317,10 +317,10 @@ class TestPostgresql(unittest.TestCase):
def test_checkpoint(self): def test_checkpoint(self):
with patch.object(MockCursor, 'fetchone', Mock(return_value=(True, ))): with patch.object(MockCursor, 'fetchone', Mock(return_value=(True, ))):
self.assertEquals(self.p.checkpoint({'user': 'postgres'}), 'is_in_recovery=true') self.assertEqual(self.p.checkpoint({'user': 'postgres'}), 'is_in_recovery=true')
with patch.object(MockCursor, 'execute', Mock(return_value=None)): with patch.object(MockCursor, 'execute', Mock(return_value=None)):
self.assertIsNone(self.p.checkpoint()) self.assertIsNone(self.p.checkpoint())
self.assertEquals(self.p.checkpoint(), 'not accessible or not healty') self.assertEqual(self.p.checkpoint(), 'not accessible or not healty')
@patch.object(Postgresql, 'cancellable_subprocess_call') @patch.object(Postgresql, 'cancellable_subprocess_call')
@patch('patroni.postgresql.Postgresql.write_pgpass', MagicMock(return_value=dict())) @patch('patroni.postgresql.Postgresql.write_pgpass', MagicMock(return_value=dict()))
@@ -401,7 +401,8 @@ class TestPostgresql(unittest.TestCase):
@patch.object(Postgresql, 'is_running', Mock(return_value=False)) @patch.object(Postgresql, 'is_running', Mock(return_value=False))
@patch.object(Postgresql, 'start', Mock()) @patch.object(Postgresql, 'start', Mock())
def test_follow(self): def test_follow(self):
self.p.follow(RemoteMember('123', {'recovery_command': 'foo'})) m = RemoteMember('1', {'restore_command': '2', 'recovery_min_apply_delay': 3, 'archive_cleanup_command': '4'})
self.p.follow(m)
@patch('subprocess.check_output', Mock(return_value=0, side_effect=pg_controldata_string)) @patch('subprocess.check_output', Mock(return_value=0, side_effect=pg_controldata_string))
def test_can_rewind(self): def test_can_rewind(self):
@@ -423,13 +424,13 @@ class TestPostgresql(unittest.TestCase):
self.p.config['create_replica_methods'] = ['wale', 'basebackup'] self.p.config['create_replica_methods'] = ['wale', 'basebackup']
self.p.config['wale'] = {'command': 'foo'} self.p.config['wale'] = {'command': 'foo'}
mock_cancellable_subprocess_call.return_value = 0 mock_cancellable_subprocess_call.return_value = 0
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
del self.p.config['wale'] del self.p.config['wale']
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.config['create_replica_methods'] = ['basebackup'] self.p.config['create_replica_methods'] = ['basebackup']
self.p.config['basebackup'] = [{'max_rate': '100M'}, 'no-sync'] self.p.config['basebackup'] = [{'max_rate': '100M'}, 'no-sync']
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.config['basebackup'] = [{'max_rate': '100M', 'compress': '9'}] self.p.config['basebackup'] = [{'max_rate': '100M', 'compress': '9'}]
with mock.patch('patroni.postgresql.logger.error', new_callable=Mock()) as mock_logger: with mock.patch('patroni.postgresql.logger.error', new_callable=Mock()) as mock_logger:
@@ -446,24 +447,24 @@ class TestPostgresql(unittest.TestCase):
"not matching {0}".format(mock_logger.call_args[0][0])) "not matching {0}".format(mock_logger.call_args[0][0]))
self.p.config['basebackup'] = {"foo": "bar"} self.p.config['basebackup'] = {"foo": "bar"}
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.config['create_replica_methods'] = ['wale', 'basebackup'] self.p.config['create_replica_methods'] = ['wale', 'basebackup']
del self.p.config['basebackup'] del self.p.config['basebackup']
mock_cancellable_subprocess_call.return_value = 1 mock_cancellable_subprocess_call.return_value = 1
self.assertEquals(self.p.create_replica(self.leader), 1) self.assertEqual(self.p.create_replica(self.leader), 1)
mock_cancellable_subprocess_call.side_effect = Exception('foo') mock_cancellable_subprocess_call.side_effect = Exception('foo')
self.assertEquals(self.p.create_replica(self.leader), 1) self.assertEqual(self.p.create_replica(self.leader), 1)
mock_cancellable_subprocess_call.side_effect = [1, 0] mock_cancellable_subprocess_call.side_effect = [1, 0]
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
mock_cancellable_subprocess_call.side_effect = [Exception(), 0] mock_cancellable_subprocess_call.side_effect = [Exception(), 0]
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.cancel() self.p.cancel()
self.assertEquals(self.p.create_replica(self.leader), 1) self.assertEqual(self.p.create_replica(self.leader), 1)
@patch('time.sleep', Mock()) @patch('time.sleep', Mock())
@patch.object(Postgresql, 'cancellable_subprocess_call') @patch.object(Postgresql, 'cancellable_subprocess_call')
@@ -477,18 +478,18 @@ class TestPostgresql(unittest.TestCase):
self.p.config['create_replica_method'] = ['wale', 'basebackup'] self.p.config['create_replica_method'] = ['wale', 'basebackup']
self.p.config['wale'] = {'command': 'foo'} self.p.config['wale'] = {'command': 'foo'}
mock_cancellable_subprocess_call.return_value = 0 mock_cancellable_subprocess_call.return_value = 0
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
del self.p.config['wale'] del self.p.config['wale']
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.config['create_replica_method'] = ['basebackup'] self.p.config['create_replica_method'] = ['basebackup']
self.p.config['basebackup'] = [{'max_rate': '100M'}, 'no-sync'] self.p.config['basebackup'] = [{'max_rate': '100M'}, 'no-sync']
self.assertEquals(self.p.create_replica(self.leader), 0) self.assertEqual(self.p.create_replica(self.leader), 0)
self.p.config['create_replica_method'] = ['wale', 'basebackup'] self.p.config['create_replica_method'] = ['wale', 'basebackup']
del self.p.config['basebackup'] del self.p.config['basebackup']
mock_cancellable_subprocess_call.return_value = 1 mock_cancellable_subprocess_call.return_value = 1
self.assertEquals(self.p.create_replica(self.leader), 1) self.assertEqual(self.p.create_replica(self.leader), 1)
def test_basebackup(self): def test_basebackup(self):
self.p.cancel() self.p.cancel()
@@ -549,25 +550,25 @@ class TestPostgresql(unittest.TestCase):
self.assertTrue(self.p.promote(0)) self.assertTrue(self.p.promote(0))
def test_timeline_wal_position(self): def test_timeline_wal_position(self):
self.assertEquals(self.p.timeline_wal_position(), (1, 2)) self.assertEqual(self.p.timeline_wal_position(), (1, 2))
Thread(target=self.p.timeline_wal_position).start() Thread(target=self.p.timeline_wal_position).start()
@patch.object(PostmasterProcess, 'from_pidfile') @patch.object(PostmasterProcess, 'from_pidfile')
def test_is_running(self, mock_frompidfile): def test_is_running(self, mock_frompidfile):
# Cached postmaster running # Cached postmaster running
mock_postmaster = self.p._postmaster_proc = MockPostmaster() mock_postmaster = self.p._postmaster_proc = MockPostmaster()
self.assertEquals(self.p.is_running(), mock_postmaster) self.assertEqual(self.p.is_running(), mock_postmaster)
# Cached postmaster not running, no postmaster running # Cached postmaster not running, no postmaster running
mock_postmaster.is_running.return_value = False mock_postmaster.is_running.return_value = False
mock_frompidfile.return_value = None mock_frompidfile.return_value = None
self.assertEquals(self.p.is_running(), None) self.assertEqual(self.p.is_running(), None)
self.assertEquals(self.p._postmaster_proc, None) self.assertEqual(self.p._postmaster_proc, None)
# No cached postmaster, postmaster running # No cached postmaster, postmaster running
mock_frompidfile.return_value = mock_postmaster2 = MockPostmaster() mock_frompidfile.return_value = mock_postmaster2 = MockPostmaster()
self.assertEquals(self.p.is_running(), mock_postmaster2) self.assertEqual(self.p.is_running(), mock_postmaster2)
self.assertEquals(self.p._postmaster_proc, mock_postmaster2) self.assertEqual(self.p._postmaster_proc, mock_postmaster2)
@patch('shlex.split', Mock(side_effect=OSError)) @patch('shlex.split', Mock(side_effect=OSError))
def test_call_nowait(self): def test_call_nowait(self):
@@ -686,13 +687,13 @@ class TestPostgresql(unittest.TestCase):
mock_cancellable_subprocess_call.assert_called() mock_cancellable_subprocess_call.assert_called()
args, kwargs = mock_cancellable_subprocess_call.call_args args, kwargs = mock_cancellable_subprocess_call.call_args
self.assertTrue('PGPASSFILE' in kwargs['env']) self.assertTrue('PGPASSFILE' in kwargs['env'])
self.assertEquals(args[0], ['/bin/false', 'postgres://127.0.0.2:5432/postgres']) self.assertEqual(args[0], ['/bin/false', 'postgres://127.0.0.2:5432/postgres'])
mock_cancellable_subprocess_call.reset_mock() mock_cancellable_subprocess_call.reset_mock()
self.p._local_address.pop('host') self.p._local_address.pop('host')
self.assertTrue(self.p.run_bootstrap_post_init({'post_init': '/bin/false'})) self.assertTrue(self.p.run_bootstrap_post_init({'post_init': '/bin/false'}))
mock_cancellable_subprocess_call.assert_called() mock_cancellable_subprocess_call.assert_called()
self.assertEquals(mock_cancellable_subprocess_call.call_args[0][0], ['/bin/false', 'postgres://:5432/postgres']) self.assertEqual(mock_cancellable_subprocess_call.call_args[0][0], ['/bin/false', 'postgres://:5432/postgres'])
mock_cancellable_subprocess_call.side_effect = OSError mock_cancellable_subprocess_call.side_effect = OSError
self.assertFalse(self.p.run_bootstrap_post_init({'post_init': '/bin/false'})) self.assertFalse(self.p.run_bootstrap_post_init({'post_init': '/bin/false'}))
@@ -704,7 +705,7 @@ class TestPostgresql(unittest.TestCase):
@patch('os.listdir', Mock(return_value=['recovery.conf'])) @patch('os.listdir', Mock(return_value=['recovery.conf']))
@patch('os.path.exists', Mock(return_value=True)) @patch('os.path.exists', Mock(return_value=True))
def test_get_postgres_role_from_data_directory(self): def test_get_postgres_role_from_data_directory(self):
self.assertEquals(self.p.get_postgres_role_from_data_directory(), 'replica') self.assertEqual(self.p.get_postgres_role_from_data_directory(), 'replica')
def test_remove_data_directory(self): def test_remove_data_directory(self):
self.p.remove_data_directory() self.p.remove_data_directory()
@@ -719,13 +720,13 @@ class TestPostgresql(unittest.TestCase):
def test_controldata(self): def test_controldata(self):
with patch('subprocess.check_output', Mock(return_value=0, side_effect=pg_controldata_string)): with patch('subprocess.check_output', Mock(return_value=0, side_effect=pg_controldata_string)):
data = self.p.controldata() data = self.p.controldata()
self.assertEquals(len(data), 50) self.assertEqual(len(data), 50)
self.assertEquals(data['Database cluster state'], 'shut down in recovery') self.assertEqual(data['Database cluster state'], 'shut down in recovery')
self.assertEquals(data['wal_log_hints setting'], 'on') self.assertEqual(data['wal_log_hints setting'], 'on')
self.assertEquals(int(data['Database block size']), 8192) self.assertEqual(int(data['Database block size']), 8192)
with patch('subprocess.check_output', Mock(side_effect=subprocess.CalledProcessError(1, ''))): with patch('subprocess.check_output', Mock(side_effect=subprocess.CalledProcessError(1, ''))):
self.assertEquals(self.p.controldata(), {}) self.assertEqual(self.p.controldata(), {})
@patch('patroni.postgresql.Postgresql._version_file_exists', Mock(return_value=True)) @patch('patroni.postgresql.Postgresql._version_file_exists', Mock(return_value=True))
@patch('subprocess.check_output', MagicMock(return_value=0, side_effect=pg_controldata_string)) @patch('subprocess.check_output', MagicMock(return_value=0, side_effect=pg_controldata_string))
@@ -779,9 +780,9 @@ class TestPostgresql(unittest.TestCase):
@patch.object(Postgresql, '_version_file_exists', Mock(return_value=True)) @patch.object(Postgresql, '_version_file_exists', Mock(return_value=True))
def test_get_major_version(self): def test_get_major_version(self):
with patch.object(builtins, 'open', mock_open(read_data='9.4')): with patch.object(builtins, 'open', mock_open(read_data='9.4')):
self.assertEquals(self.p.get_major_version(), 90400) self.assertEqual(self.p.get_major_version(), 90400)
with patch.object(builtins, 'open', Mock(side_effect=Exception)): with patch.object(builtins, 'open', Mock(side_effect=Exception)):
self.assertEquals(self.p.get_major_version(), 0) self.assertEqual(self.p.get_major_version(), 0)
def test_postmaster_start_time(self): def test_postmaster_start_time(self):
with patch.object(MockCursor, "fetchone", Mock(return_value=('foo', True, '', '', '', '', False))): with patch.object(MockCursor, "fetchone", Mock(return_value=('foo', True, '', '', '', '', False))):
@@ -793,31 +794,31 @@ class TestPostgresql(unittest.TestCase):
with patch('subprocess.call', return_value=0): with patch('subprocess.call', return_value=0):
self.p._state = 'starting' self.p._state = 'starting'
self.assertFalse(self.p.check_for_startup()) self.assertFalse(self.p.check_for_startup())
self.assertEquals(self.p.state, 'running') self.assertEqual(self.p.state, 'running')
with patch('subprocess.call', return_value=1): with patch('subprocess.call', return_value=1):
self.p._state = 'starting' self.p._state = 'starting'
self.assertTrue(self.p.check_for_startup()) self.assertTrue(self.p.check_for_startup())
self.assertEquals(self.p.state, 'starting') self.assertEqual(self.p.state, 'starting')
with patch('subprocess.call', return_value=2): with patch('subprocess.call', return_value=2):
self.p._state = 'starting' self.p._state = 'starting'
self.assertFalse(self.p.check_for_startup()) self.assertFalse(self.p.check_for_startup())
self.assertEquals(self.p.state, 'start failed') self.assertEqual(self.p.state, 'start failed')
with patch('subprocess.call', return_value=0): with patch('subprocess.call', return_value=0):
self.p._state = 'running' self.p._state = 'running'
self.assertFalse(self.p.check_for_startup()) self.assertFalse(self.p.check_for_startup())
self.assertEquals(self.p.state, 'running') self.assertEqual(self.p.state, 'running')
with patch('subprocess.call', return_value=127): with patch('subprocess.call', return_value=127):
self.p._state = 'running' self.p._state = 'running'
self.assertFalse(self.p.check_for_startup()) self.assertFalse(self.p.check_for_startup())
self.assertEquals(self.p.state, 'running') self.assertEqual(self.p.state, 'running')
self.p._state = 'starting' self.p._state = 'starting'
self.assertFalse(self.p.check_for_startup()) self.assertFalse(self.p.check_for_startup())
self.assertEquals(self.p.state, 'running') self.assertEqual(self.p.state, 'running')
def test_wait_for_startup(self): def test_wait_for_startup(self):
state = {'sleeps': 0, 'num_rejects': 0, 'final_return': 0} state = {'sleeps': 0, 'num_rejects': 0, 'final_return': 0}
@@ -842,12 +843,12 @@ class TestPostgresql(unittest.TestCase):
self.p._state = 'stopped' self.p._state = 'stopped'
self.assertTrue(self.p.wait_for_startup()) self.assertTrue(self.p.wait_for_startup())
self.assertEquals(state['sleeps'], 0) self.assertEqual(state['sleeps'], 0)
self.p._state = 'starting' self.p._state = 'starting'
state['num_rejects'] = 5 state['num_rejects'] = 5
self.assertTrue(self.p.wait_for_startup()) self.assertTrue(self.p.wait_for_startup())
self.assertEquals(state['sleeps'], 5) self.assertEqual(state['sleeps'], 5)
self.p._state = 'starting' self.p._state = 'starting'
state['sleeps'] = 0 state['sleeps'] = 0
@@ -858,7 +859,7 @@ class TestPostgresql(unittest.TestCase):
state['sleeps'] = 0 state['sleeps'] = 0
state['final_return'] = 0 state['final_return'] = 0
self.assertFalse(self.p.wait_for_startup(timeout=2)) self.assertFalse(self.p.wait_for_startup(timeout=2))
self.assertEquals(state['sleeps'], 3) self.assertEqual(state['sleeps'], 3)
with patch.object(Postgresql, 'check_startup_state_changed', Mock(return_value=False)): with patch.object(Postgresql, 'check_startup_state_changed', Mock(return_value=False)):
self.p.cancel() self.p.cancel()
@@ -874,30 +875,30 @@ class TestPostgresql(unittest.TestCase):
(self.me.name, 'streaming', 'async'), (self.me.name, 'streaming', 'async'),
(self.other.name, 'streaming', 'async'), (self.other.name, 'streaming', 'async'),
]): ]):
self.assertEquals(self.p.pick_synchronous_standby(cluster), (self.leadermem.name, True)) self.assertEqual(self.p.pick_synchronous_standby(cluster), (self.leadermem.name, True))
with patch.object(Postgresql, "query", return_value=[ with patch.object(Postgresql, "query", return_value=[
(self.me.name, 'streaming', 'async'), (self.me.name, 'streaming', 'async'),
(self.leadermem.name, 'streaming', 'potential'), (self.leadermem.name, 'streaming', 'potential'),
(self.other.name, 'streaming', 'async'), (self.other.name, 'streaming', 'async'),
]): ]):
self.assertEquals(self.p.pick_synchronous_standby(cluster), (self.leadermem.name, False)) self.assertEqual(self.p.pick_synchronous_standby(cluster), (self.leadermem.name, False))
with patch.object(Postgresql, "query", return_value=[ with patch.object(Postgresql, "query", return_value=[
(self.me.name, 'streaming', 'async'), (self.me.name, 'streaming', 'async'),
(self.other.name, 'streaming', 'async'), (self.other.name, 'streaming', 'async'),
]): ]):
self.assertEquals(self.p.pick_synchronous_standby(cluster), (self.me.name, False)) self.assertEqual(self.p.pick_synchronous_standby(cluster), (self.me.name, False))
with patch.object(Postgresql, "query", return_value=[ with patch.object(Postgresql, "query", return_value=[
('missing', 'streaming', 'sync'), ('missing', 'streaming', 'sync'),
(self.me.name, 'streaming', 'async'), (self.me.name, 'streaming', 'async'),
(self.other.name, 'streaming', 'async'), (self.other.name, 'streaming', 'async'),
]): ]):
self.assertEquals(self.p.pick_synchronous_standby(cluster), (self.me.name, False)) self.assertEqual(self.p.pick_synchronous_standby(cluster), (self.me.name, False))
with patch.object(Postgresql, "query", return_value=[]): with patch.object(Postgresql, "query", return_value=[]):
self.assertEquals(self.p.pick_synchronous_standby(cluster), (None, False)) self.assertEqual(self.p.pick_synchronous_standby(cluster), (None, False))
def test_set_sync_standby(self): def test_set_sync_standby(self):
def value_in_conf(): def value_in_conf():
@@ -908,22 +909,22 @@ class TestPostgresql(unittest.TestCase):
mock_reload = self.p.reload = Mock() mock_reload = self.p.reload = Mock()
self.p.set_synchronous_standby('n1') self.p.set_synchronous_standby('n1')
self.assertEquals(value_in_conf(), "synchronous_standby_names = 'n1'") self.assertEqual(value_in_conf(), "synchronous_standby_names = 'n1'")
mock_reload.assert_called() mock_reload.assert_called()
mock_reload.reset_mock() mock_reload.reset_mock()
self.p.set_synchronous_standby('n1') self.p.set_synchronous_standby('n1')
mock_reload.assert_not_called() mock_reload.assert_not_called()
self.assertEquals(value_in_conf(), "synchronous_standby_names = 'n1'") self.assertEqual(value_in_conf(), "synchronous_standby_names = 'n1'")
self.p.set_synchronous_standby('n2') self.p.set_synchronous_standby('n2')
mock_reload.assert_called() mock_reload.assert_called()
self.assertEquals(value_in_conf(), "synchronous_standby_names = 'n2'") self.assertEqual(value_in_conf(), "synchronous_standby_names = 'n2'")
mock_reload.reset_mock() mock_reload.reset_mock()
self.p.set_synchronous_standby(None) self.p.set_synchronous_standby(None)
mock_reload.assert_called() mock_reload.assert_called()
self.assertEquals(value_in_conf(), None) self.assertEqual(value_in_conf(), None)
def test_get_server_parameters(self): def test_get_server_parameters(self):
config = {'synchronous_mode': True, 'parameters': {'wal_level': 'hot_standby'}, 'listen': '0'} config = {'synchronous_mode': True, 'parameters': {'wal_level': 'hot_standby'}, 'listen': '0'}
@@ -957,8 +958,8 @@ class TestPostgresql(unittest.TestCase):
"--wal_log_hints=on" "--max_wal_senders=5" "--max_replication_slots=5"\n') "--wal_log_hints=on" "--max_wal_senders=5" "--max_replication_slots=5"\n')
with patch.object(builtins, 'open', m): with patch.object(builtins, 'open', m):
data = self.p.read_postmaster_opts() data = self.p.read_postmaster_opts()
self.assertEquals(data['wal_level'], 'hot_standby') self.assertEqual(data['wal_level'], 'hot_standby')
self.assertEquals(int(data['max_replication_slots']), 5) self.assertEqual(int(data['max_replication_slots']), 5)
self.assertEqual(data.get('D'), None) self.assertEqual(data.get('D'), None)
m.side_effect = IOError m.side_effect = IOError
@@ -968,7 +969,7 @@ class TestPostgresql(unittest.TestCase):
@patch('subprocess.Popen') @patch('subprocess.Popen')
def test_single_user_mode(self, subprocess_popen_mock): def test_single_user_mode(self, subprocess_popen_mock):
subprocess_popen_mock.return_value.wait.return_value = 0 subprocess_popen_mock.return_value.wait.return_value = 0
self.assertEquals(self.p.single_user_mode('CHECKPOINT', {'archive_mode': 'on'}), 0) self.assertEqual(self.p.single_user_mode('CHECKPOINT', {'archive_mode': 'on'}), 0)
@patch('os.listdir', Mock(side_effect=[OSError, ['a', 'b']])) @patch('os.listdir', Mock(side_effect=[OSError, ['a', 'b']]))
@patch('os.unlink', Mock(side_effect=OSError)) @patch('os.unlink', Mock(side_effect=OSError))
@@ -986,10 +987,10 @@ class TestPostgresql(unittest.TestCase):
self.assertTrue(self.p.fix_cluster_state()) self.assertTrue(self.p.fix_cluster_state())
def test_replica_cached_timeline(self): def test_replica_cached_timeline(self):
self.assertEquals(self.p.replica_cached_timeline(1), 2) self.assertEqual(self.p.replica_cached_timeline(1), 2)
def test_get_master_timeline(self): def test_get_master_timeline(self):
self.assertEquals(self.p.get_master_timeline(), 1) self.assertEqual(self.p.get_master_timeline(), 1)
def test_cancellable_subprocess_call(self): def test_cancellable_subprocess_call(self):
self.p.cancel() self.p.cancel()
+7 -7
View File
@@ -46,7 +46,7 @@ class TestPostmasterProcess(unittest.TestCase):
@patch('psutil.Process.__init__') @patch('psutil.Process.__init__')
def test_from_pid(self, mock_init): def test_from_pid(self, mock_init):
mock_init.side_effect = psutil.NoSuchProcess(123) mock_init.side_effect = psutil.NoSuchProcess(123)
self.assertEquals(PostmasterProcess.from_pid(123), None) self.assertEqual(PostmasterProcess.from_pid(123), None)
mock_init.side_effect = None mock_init.side_effect = None
self.assertNotEquals(PostmasterProcess.from_pid(123), None) self.assertNotEquals(PostmasterProcess.from_pid(123), None)
@@ -55,13 +55,13 @@ class TestPostmasterProcess(unittest.TestCase):
@patch('psutil.Process.pid', Mock(return_value=123)) @patch('psutil.Process.pid', Mock(return_value=123))
def test_signal_stop(self, mock_send_signal): def test_signal_stop(self, mock_send_signal):
proc = PostmasterProcess(-123) proc = PostmasterProcess(-123)
self.assertEquals(proc.signal_stop('immediate'), False) self.assertEqual(proc.signal_stop('immediate'), False)
mock_send_signal.side_effect = [None, psutil.NoSuchProcess(123), psutil.AccessDenied()] mock_send_signal.side_effect = [None, psutil.NoSuchProcess(123), psutil.AccessDenied()]
proc = PostmasterProcess(123) proc = PostmasterProcess(123)
self.assertEquals(proc.signal_stop('immediate'), None) self.assertEqual(proc.signal_stop('immediate'), None)
self.assertEquals(proc.signal_stop('immediate'), True) self.assertEqual(proc.signal_stop('immediate'), True)
self.assertEquals(proc.signal_stop('immediate'), False) self.assertEqual(proc.signal_stop('immediate'), False)
@patch('psutil.Process.__init__', Mock()) @patch('psutil.Process.__init__', Mock())
@patch('psutil.wait_procs') @patch('psutil.wait_procs')
@@ -89,11 +89,11 @@ class TestPostmasterProcess(unittest.TestCase):
mock_frompidfile.return_value._is_postmaster_process.return_value = False mock_frompidfile.return_value._is_postmaster_process.return_value = False
mock_frompid.return_value = "proc 123" mock_frompid.return_value = "proc 123"
mock_popen.return_value.stdout.readline.return_value = '123' mock_popen.return_value.stdout.readline.return_value = '123'
self.assertEquals(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123") self.assertEqual(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123")
mock_frompid.assert_called_with(123) mock_frompid.assert_called_with(123)
mock_frompidfile.side_effect = psutil.NoSuchProcess(123) mock_frompidfile.side_effect = psutil.NoSuchProcess(123)
self.assertEquals(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123") self.assertEqual(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123")
@patch('psutil.Process.__init__', Mock(side_effect=psutil.NoSuchProcess(123))) @patch('psutil.Process.__init__', Mock(side_effect=psutil.NoSuchProcess(123)))
def test_read_postmaster_pidfile(self): def test_read_postmaster_pidfile(self):
+5 -5
View File
@@ -8,7 +8,7 @@ from patroni.utils import Retry, RetryFailedError, polling_loop
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
def test_polling_loop(self): def test_polling_loop(self):
self.assertEquals(list(polling_loop(0.001, interval=0.001)), [0]) self.assertEqual(list(polling_loop(0.001, interval=0.001)), [0])
@patch('time.sleep', Mock()) @patch('time.sleep', Mock())
@@ -29,21 +29,21 @@ class TestRetrySleeper(unittest.TestCase):
def test_reset(self): def test_reset(self):
retry = Retry(delay=0, max_tries=2) retry = Retry(delay=0, max_tries=2)
retry(self._fail()) retry(self._fail())
self.assertEquals(retry._attempts, 1) self.assertEqual(retry._attempts, 1)
retry.reset() retry.reset()
self.assertEquals(retry._attempts, 0) self.assertEqual(retry._attempts, 0)
def test_too_many_tries(self): def test_too_many_tries(self):
retry = Retry(delay=0) retry = Retry(delay=0)
self.assertRaises(RetryFailedError, retry, self._fail(times=999)) self.assertRaises(RetryFailedError, retry, self._fail(times=999))
self.assertEquals(retry._attempts, 1) self.assertEqual(retry._attempts, 1)
def test_maximum_delay(self): def test_maximum_delay(self):
retry = Retry(delay=10, max_tries=100) retry = Retry(delay=10, max_tries=100)
retry(self._fail(times=10)) retry(self._fail(times=10))
self.assertTrue(retry._cur_delay < 4000, retry._cur_delay) self.assertTrue(retry._cur_delay < 4000, retry._cur_delay)
# gevent's sleep function is picky about the type # gevent's sleep function is picky about the type
self.assertEquals(type(retry._cur_delay), float) self.assertEqual(type(retry._cur_delay), float)
def test_deadline(self): def test_deadline(self):
retry = Retry(deadline=0.0001) retry = Retry(deadline=0.0001)
+11 -11
View File
@@ -73,14 +73,14 @@ class TestWatchdog(unittest.TestCase):
@patch.object(LinuxWatchdogDevice, 'can_be_disabled', PropertyMock(return_value=True)) @patch.object(LinuxWatchdogDevice, 'can_be_disabled', PropertyMock(return_value=True))
def test_unsafe_timeout_disable_watchdog_and_exit(self): def test_unsafe_timeout_disable_watchdog_and_exit(self):
watchdog = Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'required', 'safety_margin': -1}}) watchdog = Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'required', 'safety_margin': -1}})
self.assertEquals(watchdog.activate(), False) self.assertEqual(watchdog.activate(), False)
self.assertEquals(watchdog.is_running, False) self.assertEqual(watchdog.is_running, False)
@patch('platform.system', Mock(return_value='Linux')) @patch('platform.system', Mock(return_value='Linux'))
@patch.object(LinuxWatchdogDevice, 'get_timeout', Mock(return_value=16)) @patch.object(LinuxWatchdogDevice, 'get_timeout', Mock(return_value=16))
def test_timeout_does_not_ensure_safe_termination(self): def test_timeout_does_not_ensure_safe_termination(self):
Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'auto', 'safety_margin': -1}}).activate() Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'auto', 'safety_margin': -1}}).activate()
self.assertEquals(len(mock_devices), 2) self.assertEqual(len(mock_devices), 2)
@patch('platform.system', Mock(return_value='Linux')) @patch('platform.system', Mock(return_value='Linux'))
@patch.object(Watchdog, 'is_running', PropertyMock(return_value=False)) @patch.object(Watchdog, 'is_running', PropertyMock(return_value=False))
@@ -99,29 +99,29 @@ class TestWatchdog(unittest.TestCase):
watchdog = Watchdog({'ttl': 30, 'loop_wait': 10, 'watchdog': {'mode': 'required'}}) watchdog = Watchdog({'ttl': 30, 'loop_wait': 10, 'watchdog': {'mode': 'required'}})
watchdog.activate() watchdog.activate()
self.assertEquals(len(mock_devices), 2) self.assertEqual(len(mock_devices), 2)
device = mock_devices[-1] device = mock_devices[-1]
self.assertTrue(device.open) self.assertTrue(device.open)
self.assertEquals(device.timeout, 24) self.assertEqual(device.timeout, 24)
watchdog.keepalive() watchdog.keepalive()
self.assertEquals(len(device.writes), 1) self.assertEqual(len(device.writes), 1)
watchdog.disable() watchdog.disable()
self.assertFalse(device.open) self.assertFalse(device.open)
self.assertEquals(device.writes[-1], b'V') self.assertEqual(device.writes[-1], b'V')
def test_invalid_timings(self): def test_invalid_timings(self):
watchdog = Watchdog({'ttl': 30, 'loop_wait': 20, 'watchdog': {'mode': 'automatic', 'safety_margin': -1}}) watchdog = Watchdog({'ttl': 30, 'loop_wait': 20, 'watchdog': {'mode': 'automatic', 'safety_margin': -1}})
watchdog.activate() watchdog.activate()
self.assertEquals(len(mock_devices), 1) self.assertEqual(len(mock_devices), 1)
self.assertFalse(watchdog.is_running) self.assertFalse(watchdog.is_running)
def test_parse_mode(self): def test_parse_mode(self):
with patch('patroni.watchdog.base.logger.warning', new_callable=Mock()) as warning_mock: with patch('patroni.watchdog.base.logger.warning', new_callable=Mock()) as warning_mock:
watchdog = Watchdog({'ttl': 30, 'loop_wait': 10, 'watchdog': {'mode': 'bad'}}) watchdog = Watchdog({'ttl': 30, 'loop_wait': 10, 'watchdog': {'mode': 'bad'}})
self.assertEquals(watchdog.config.mode, 'off') self.assertEqual(watchdog.config.mode, 'off')
warning_mock.assert_called_once() warning_mock.assert_called_once()
@patch('platform.system', Mock(return_value='Unknown')) @patch('platform.system', Mock(return_value='Unknown'))
@@ -170,7 +170,7 @@ class TestNullWatchdog(unittest.TestCase):
watchdog = NullWatchdog() watchdog = NullWatchdog()
self.assertTrue(watchdog.can_be_disabled) self.assertTrue(watchdog.can_be_disabled)
self.assertRaises(WatchdogError, watchdog.set_timeout, 1) self.assertRaises(WatchdogError, watchdog.set_timeout, 1)
self.assertEquals(watchdog.describe(), 'NullWatchdog') self.assertEqual(watchdog.describe(), 'NullWatchdog')
self.assertIsInstance(NullWatchdog.from_config({}), NullWatchdog) self.assertIsInstance(NullWatchdog.from_config({}), NullWatchdog)
@@ -210,7 +210,7 @@ class TestLinuxWatchdogDevice(unittest.TestCase):
self.assertRaises(WatchdogError, self.impl.get_timeout) self.assertRaises(WatchdogError, self.impl.get_timeout)
self.assertRaises(WatchdogError, self.impl.set_timeout, 10) self.assertRaises(WatchdogError, self.impl.set_timeout, 10)
# We still try to output a reasonable string even if getting info errors # We still try to output a reasonable string even if getting info errors
self.assertEquals(self.impl.describe(), "Linux watchdog device") self.assertEqual(self.impl.describe(), "Linux watchdog device")
@patch('os.open', Mock(side_effect=OSError)) @patch('os.open', Mock(side_effect=OSError))
def test_open(self): def test_open(self):