From e375fac273a75457bb241ade28c1609bf5a7fca2 Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin Date: Wed, 4 Apr 2018 14:23:53 +0200 Subject: [PATCH] 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 --- patroni/config.py | 3 ++- patroni/postgresql.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/patroni/config.py b/patroni/config.py index 5dabe8ec..8a1a5aa8 100644 --- a/patroni/config.py +++ b/patroni/config.py @@ -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', diff --git a/patroni/postgresql.py b/patroni/postgresql.py index a4a076d4..b52b7068 100644 --- a/patroni/postgresql.py +++ b/patroni/postgresql.py @@ -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)