Treat postgres settings parameter names as case insensitive (#650)

Because they are indeed case insensitive.
Most of the parameters have snake_case_name, but there are three exceptions from this rule: DateStyle, IntervalStyle and TimeZone.
In fact, if you specify timezone = 'some/tzn' it still works, but Patroni wasn't able to find 'timezone' in pg_settings and stripping this parameter out.

We will use CaseInsensitiveDict to keep postgresql.parameters. This change affects only "final" configuration. That means if you put some"duplicates" (work_mem vs WORK_MEM) into patroni yaml or into cluster config, it would be resolved only at the last stage and for example you will be able to see both values if you use `patronictl edit-config`.

Fixes https://github.com/zalando/patroni/issues/649
This commit is contained in:
Alexander Kukushkin
2018-04-04 14:23:53 +02:00
committed by GitHub
parent 8c795ff0cf
commit e375fac273
2 changed files with 12 additions and 12 deletions
+2 -1
View File
@@ -10,6 +10,7 @@ from copy import deepcopy
from patroni.dcs import ClusterConfig
from patroni.postgresql import Postgresql
from patroni.utils import deep_compare, parse_int, patch_config
from requests.structures import CaseInsensitiveDict
logger = logging.getLogger(__name__)
@@ -47,7 +48,7 @@ class Config(object):
'postgresql': {
'bin_dir': '',
'use_slots': True,
'parameters': {p: v[0] for p, v in Postgresql.CMDLINE_OPTIONS.items()}
'parameters': CaseInsensitiveDict({p: v[0] for p, v in Postgresql.CMDLINE_OPTIONS.items()})
},
'watchdog': {
'mode': 'automatic',
+10 -11
View File
@@ -15,6 +15,7 @@ from patroni.callback_executor import CallbackExecutor
from patroni.exceptions import PostgresConnectionException, PostgresException
from patroni.utils import compare_values, parse_bool, parse_int, Retry, RetryFailedError, polling_loop, split_host_port
from patroni.postmaster import PostmasterProcess
from requests.structures import CaseInsensitiveDict
from six import string_types
from six.moves.urllib.parse import quote_plus
from threading import current_thread, Lock
@@ -86,7 +87,7 @@ class Postgresql(object):
# default_value -- some sane default value
# check_function -- if the new value is not correct must return `!False`
# min_version -- major version of PostgreSQL when parameter was introduced
CMDLINE_OPTIONS = {
CMDLINE_OPTIONS = CaseInsensitiveDict({
'listen_addresses': (None, lambda _: False, 90100),
'port': (None, lambda _: False, 90100),
'cluster_name': (None, lambda _: False, 90500),
@@ -101,7 +102,7 @@ class Postgresql(object):
'max_replication_slots': (10, lambda v: int(v) >= 10, 90400),
'max_worker_processes': (8, lambda v: int(v) >= 8, 90400),
'wal_log_hints': ('on', lambda _: False, 90400)
}
})
_CONFIG_WARNING_HEADER = '# Do not edit this file manually!\n# It will be overwritten by Patroni!\n'
@@ -241,11 +242,9 @@ class Postgresql(object):
parameters['synchronous_standby_names'] = self._synchronous_standby_names
if self._major_version >= 90600 and parameters['wal_level'] == 'hot_standby':
parameters['wal_level'] = 'replica'
ret = {k: v for k, v in parameters.items() if not self._major_version or
self._major_version >= self.CMDLINE_OPTIONS.get(k, (0, 1, 90100))[2]}
for k in ('hba_file', 'ident_file'):
if k in ret:
ret[k] = os.path.join(self._config_dir, ret[k])
ret = CaseInsensitiveDict({k: v for k, v in parameters.items() if not self._major_version or
self._major_version >= self.CMDLINE_OPTIONS.get(k, (0, 1, 90100))[2]})
ret.update({k: os.path.join(self._config_dir, ret[k]) for k in ('hba_file', 'ident_file') if k in ret})
return ret
def resolve_connection_addresses(self):
@@ -310,16 +309,16 @@ class Postgresql(object):
conf_changed = hba_changed = local_connection_address_changed = pending_restart = False
if self.state == 'running':
changes = {p: v for p, v in server_parameters.items() if '.' not in p}
changes.update({p: None for p, v in self._server_parameters.items() if not ('.' in p or p in changes)})
changes = CaseInsensitiveDict({p: v for p, v in server_parameters.items() if '.' not in p})
changes.update({p: None for p in self._server_parameters.keys() if not ('.' in p or p in changes)})
if changes:
if 'wal_segment_size' not in changes:
changes['wal_segment_size'] = '16384kB'
# XXX: query can raise an exception
for r in self.query("""SELECT name, setting, unit, vartype, context
FROM pg_settings
WHERE name IN (""" + ', '.join(['%s'] * len(changes)) + """)
ORDER BY 1 DESC""", *(list(changes.keys()))):
WHERE LOWER(name) IN (""" + ', '.join(['%s'] * len(changes)) + """)
ORDER BY 1 DESC""", *(k.lower() for k in changes.keys())):
if r[4] == 'internal':
if r[0] == 'wal_segment_size':
server_parameters.pop(r[0], None)