mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-31 08:39:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f500dbb0ff | ||
|
|
f748de3b29 | ||
|
|
3afd26101b | ||
|
|
c04e7a1798 | ||
|
|
89a11fed07 |
@@ -11,6 +11,8 @@ Global/Universal
|
|||||||
- **PATRONI\_NAME**: name of the node where the current instance of Patroni is running. Must be unique for the cluster.
|
- **PATRONI\_NAME**: name of the node where the current instance of Patroni is running. Must be unique for the cluster.
|
||||||
- **PATRONI\_NAMESPACE**: path within the configuration store where Patroni will keep information about the cluster. Default value: "/service"
|
- **PATRONI\_NAMESPACE**: path within the configuration store where Patroni will keep information about the cluster. Default value: "/service"
|
||||||
- **PATRONI\_SCOPE**: cluster name
|
- **PATRONI\_SCOPE**: cluster name
|
||||||
|
- **PATRONI\_LOGLEVEL**: sets the general logging level (see `the docs for Python logging <https://docs.python.org/3.6/library/logging.html#levels>`_)
|
||||||
|
- **PATRONI\_REQUESTS_LOGLEVEL**: sets the logging level for all HTTP requests e.g. Kubernetes API calls (see `the docs for Python logging <https://docs.python.org/3.6/library/logging.html#levels>`_)
|
||||||
|
|
||||||
Bootstrap configuration
|
Bootstrap configuration
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|||||||
@@ -3,6 +3,32 @@
|
|||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Version 1.4.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
**Improvements in logging**
|
||||||
|
|
||||||
|
- Make log level configurable from environment variables (Andy Newton, Keyvan Hedayati)
|
||||||
|
|
||||||
|
`PATRONI_LOGLEVEL` - sets the general logging level
|
||||||
|
`PATRONI_REQUESTS_LOGLEVEL` - sets the logging level for all HTTP requests e.g. Kubernetes API calls
|
||||||
|
See `the docs for Python logging <https://docs.python.org/3.6/library/logging.html#levels>` to get the names of possible log levels
|
||||||
|
|
||||||
|
**Stability improvements and bug fixes**
|
||||||
|
|
||||||
|
- Don't rediscover etcd cluster topology when watch timed out (Alexander Kukushkin)
|
||||||
|
|
||||||
|
If we have only one host in etcd configuration and exactly this host is not accessible, Patroni was starting discovery of cluster topology and never succeeding. Instead it should just switch to the next available node.
|
||||||
|
|
||||||
|
- Write content of bootstrap.pg_hba into a pg_hba.conf after custom bootstrap (Alexander)
|
||||||
|
|
||||||
|
Now it behaves similarly to the usual bootstrap with `initdb`
|
||||||
|
|
||||||
|
- Single user mode was waiting for user input and never finish (Alexander)
|
||||||
|
|
||||||
|
Regression was introduced in https://github.com/zalando/patroni/pull/576
|
||||||
|
|
||||||
|
|
||||||
Version 1.4.2
|
Version 1.4.2
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -135,8 +135,10 @@ class Patroni(object):
|
|||||||
|
|
||||||
def patroni_main():
|
def patroni_main():
|
||||||
logformat = os.environ.get('PATRONI_LOGFORMAT', '%(asctime)s %(levelname)s: %(message)s')
|
logformat = os.environ.get('PATRONI_LOGFORMAT', '%(asctime)s %(levelname)s: %(message)s')
|
||||||
logging.basicConfig(format=logformat, level=logging.INFO)
|
loglevel = os.environ.get('PATRONI_LOGLEVEL', 'INFO')
|
||||||
logging.getLogger('requests').setLevel(logging.WARNING)
|
requests_loglevel = os.environ.get('PATRONI_REQUESTS_LOGLEVEL', 'WARNING')
|
||||||
|
logging.basicConfig(format=logformat, level=loglevel)
|
||||||
|
logging.getLogger('requests').setLevel(requests_loglevel)
|
||||||
|
|
||||||
patroni = Patroni()
|
patroni = Patroni()
|
||||||
try:
|
try:
|
||||||
|
|||||||
+5
-2
@@ -210,8 +210,11 @@ class Client(etcd.Client):
|
|||||||
self._machines_cache = self.machines
|
self._machines_cache = self.machines
|
||||||
if self._base_uri in self._machines_cache:
|
if self._base_uri in self._machines_cache:
|
||||||
self._machines_cache.remove(self._base_uri)
|
self._machines_cache.remove(self._base_uri)
|
||||||
except etcd.EtcdConnectionFailed:
|
except etcd.EtcdConnectionFailed as e:
|
||||||
self._update_machines_cache = True
|
if isinstance(e, etcd.EtcdWatchTimedOut) and self._machines_cache:
|
||||||
|
self._base_uri = self._next_server()
|
||||||
|
else:
|
||||||
|
self._update_machines_cache = True
|
||||||
if not response:
|
if not response:
|
||||||
raise
|
raise
|
||||||
return self._handle_server_response(response)
|
return self._handle_server_response(response)
|
||||||
|
|||||||
+22
-15
@@ -537,12 +537,7 @@ class Postgresql(object):
|
|||||||
ret = self.pg_ctl('initdb', *options)
|
ret = self.pg_ctl('initdb', *options)
|
||||||
if pwfile:
|
if pwfile:
|
||||||
os.remove(pwfile)
|
os.remove(pwfile)
|
||||||
if ret:
|
if not ret:
|
||||||
if not self._server_parameters.get('hba_file') and not self.config.get('pg_hba'):
|
|
||||||
self.write_pg_hba(config.get('pg_hba', []))
|
|
||||||
self._major_version = self.get_major_version()
|
|
||||||
self._server_parameters = self.get_server_parameters(self.config)
|
|
||||||
else:
|
|
||||||
self.set_state('initdb failed')
|
self.set_state('initdb failed')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -558,7 +553,6 @@ class Postgresql(object):
|
|||||||
logger.exception('Exception during custom bootstrap')
|
logger.exception('Exception during custom bootstrap')
|
||||||
return False
|
return False
|
||||||
self._post_restore()
|
self._post_restore()
|
||||||
self.save_configuration_files()
|
|
||||||
|
|
||||||
if 'recovery_conf' in config:
|
if 'recovery_conf' in config:
|
||||||
self.write_recovery_conf(config['recovery_conf'])
|
self.write_recovery_conf(config['recovery_conf'])
|
||||||
@@ -1080,8 +1074,10 @@ class Postgresql(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def write_pg_hba(self, config):
|
def write_pg_hba(self, config):
|
||||||
with open(self._pg_hba_conf, 'a') as f:
|
if not self._server_parameters.get('hba_file') and not self.config.get('pg_hba'):
|
||||||
f.write('\n{}\n'.format('\n'.join(config)))
|
with open(self._pg_hba_conf, 'a') as f:
|
||||||
|
f.write('\n{}\n'.format('\n'.join(config)))
|
||||||
|
return True
|
||||||
|
|
||||||
def _replace_pg_hba(self):
|
def _replace_pg_hba(self):
|
||||||
"""
|
"""
|
||||||
@@ -1398,6 +1394,7 @@ class Postgresql(object):
|
|||||||
shutil.copy(config_file, backup_file)
|
shutil.copy(config_file, backup_file)
|
||||||
except IOError:
|
except IOError:
|
||||||
logger.exception('unable to create backup copies of configuration files')
|
logger.exception('unable to create backup copies of configuration files')
|
||||||
|
return True
|
||||||
|
|
||||||
def restore_configuration_files(self):
|
def restore_configuration_files(self):
|
||||||
""" restore a previously saved postgresql.conf """
|
""" restore a previously saved postgresql.conf """
|
||||||
@@ -1547,6 +1544,7 @@ $$""".format(name, ' '.join(options)), name, password, password)
|
|||||||
|
|
||||||
def bootstrap(self, config):
|
def bootstrap(self, config):
|
||||||
""" Initialize a new node from scratch and start it. """
|
""" Initialize a new node from scratch and start it. """
|
||||||
|
pg_hba = config.get('pg_hba', [])
|
||||||
method = config.get('method') or 'initdb'
|
method = config.get('method') or 'initdb'
|
||||||
self._running_custom_bootstrap = method != 'initdb' and method in config and 'command' in config[method]
|
self._running_custom_bootstrap = method != 'initdb' and method in config and 'command' in config[method]
|
||||||
if self._running_custom_bootstrap:
|
if self._running_custom_bootstrap:
|
||||||
@@ -1554,7 +1552,8 @@ $$""".format(name, ' '.join(options)), name, password, password)
|
|||||||
config = config[method]
|
config = config[method]
|
||||||
else:
|
else:
|
||||||
do_initialize = self._initdb
|
do_initialize = self._initdb
|
||||||
return do_initialize(config) and self._configure_server_parameters() and self.start()
|
return do_initialize(config) and self.write_pg_hba(pg_hba) and self.save_configuration_files() \
|
||||||
|
and self._configure_server_parameters() and self.start()
|
||||||
|
|
||||||
def post_bootstrap(self, config, task):
|
def post_bootstrap(self, config, task):
|
||||||
try:
|
try:
|
||||||
@@ -1791,10 +1790,20 @@ $$""".format(name, ' '.join(options)), name, password, password)
|
|||||||
return self.single_user_mode(options=opts) == 0 or None
|
return self.single_user_mode(options=opts) == 0 or None
|
||||||
|
|
||||||
def cancellable_subprocess_call(self, *args, **kwargs):
|
def cancellable_subprocess_call(self, *args, **kwargs):
|
||||||
communicate_input = kwargs.pop('communicate_input', None)
|
|
||||||
for s in ('stdin', 'stdout', 'stderr'):
|
for s in ('stdin', 'stdout', 'stderr'):
|
||||||
kwargs.pop(s, None)
|
kwargs.pop(s, None)
|
||||||
|
|
||||||
|
communicate_input = 'communicate_input' in kwargs
|
||||||
|
if communicate_input:
|
||||||
|
input_data = kwargs.pop('communicate_input', None)
|
||||||
|
if not isinstance(input_data, string_types):
|
||||||
|
input_data = ''
|
||||||
|
if input_data and input_data[-1] != '\n':
|
||||||
|
input_data += '\n'
|
||||||
|
kwargs['stdin'] = subprocess.PIPE
|
||||||
|
kwargs['stdout'] = open(os.devnull, 'w')
|
||||||
|
kwargs['stderr'] = subprocess.STDOUT
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self._cancellable_lock:
|
with self._cancellable_lock:
|
||||||
if self._is_cancelled:
|
if self._is_cancelled:
|
||||||
@@ -1804,10 +1813,8 @@ $$""".format(name, ' '.join(options)), name, password, password)
|
|||||||
self._cancellable = subprocess.Popen(*args, **kwargs)
|
self._cancellable = subprocess.Popen(*args, **kwargs)
|
||||||
|
|
||||||
if communicate_input:
|
if communicate_input:
|
||||||
kwargs['stdin'] = subprocess.PIPE
|
if input_data:
|
||||||
if communicate_input[-1] != '\n':
|
self._cancellable.communicate(input_data)
|
||||||
communicate_input += '\n'
|
|
||||||
self._cancellable.communicate(communicate_input + '\n')
|
|
||||||
self._cancellable.stdin.close()
|
self._cancellable.stdin.close()
|
||||||
|
|
||||||
return self._cancellable.wait()
|
return self._cancellable.wait()
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
__version__ = '1.4.2'
|
__version__ = '1.4.3'
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ class TestClient(unittest.TestCase):
|
|||||||
mock_machines.__get__ = Mock(return_value=['http://localhost:2379'])
|
mock_machines.__get__ = Mock(return_value=['http://localhost:2379'])
|
||||||
self.client._machines_cache_updated = 0
|
self.client._machines_cache_updated = 0
|
||||||
self.client.api_execute('/', 'POST', timeout=0)
|
self.client.api_execute('/', 'POST', timeout=0)
|
||||||
|
self.client._machines_cache = [self.client._base_uri]
|
||||||
self.assertRaises(etcd.EtcdWatchTimedOut, self.client.api_execute, '/timeout', 'POST', params={'wait': 'true'})
|
self.assertRaises(etcd.EtcdWatchTimedOut, self.client.api_execute, '/timeout', 'POST', params={'wait': 'true'})
|
||||||
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', '')
|
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', '')
|
||||||
self.client._update_machines_cache = True
|
self.client._update_machines_cache = True
|
||||||
|
|||||||
@@ -567,6 +567,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
|
|
||||||
@patch.object(Postgresql, 'cancellable_subprocess_call')
|
@patch.object(Postgresql, 'cancellable_subprocess_call')
|
||||||
def test_custom_bootstrap(self, mock_cancellable_subprocess_call):
|
def test_custom_bootstrap(self, mock_cancellable_subprocess_call):
|
||||||
|
self.p.config.pop('pg_hba')
|
||||||
config = {'method': 'foo', 'foo': {'command': 'bar'}}
|
config = {'method': 'foo', 'foo': {'command': 'bar'}}
|
||||||
|
|
||||||
mock_cancellable_subprocess_call.return_value = 1
|
mock_cancellable_subprocess_call.return_value = 1
|
||||||
@@ -945,7 +946,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
|
|
||||||
def test_cancellable_subprocess_call(self):
|
def test_cancellable_subprocess_call(self):
|
||||||
self.p.cancel()
|
self.p.cancel()
|
||||||
self.assertRaises(PostgresException, self.p.cancellable_subprocess_call)
|
self.assertRaises(PostgresException, self.p.cancellable_subprocess_call, communicate_input=None)
|
||||||
|
|
||||||
@patch('patroni.postgresql.polling_loop', Mock(return_value=[0, 0]))
|
@patch('patroni.postgresql.polling_loop', Mock(return_value=[0, 0]))
|
||||||
def test_cancel(self):
|
def test_cancel(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user