mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Use passfile in the primary_conninfo instead of password (#1194)
Fixed a few minor issues related to the #1134 and #1122 Close https://github.com/zalando/patroni/issues/1185
This commit is contained in:
+6
-6
@@ -15,14 +15,14 @@ from requests.structures import CaseInsensitiveDict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AUTH_ALLOWED_PARAMETERS = (
|
||||
_AUTH_ALLOWED_PARAMETERS = (
|
||||
'username',
|
||||
'password',
|
||||
'sslmode',
|
||||
'sslcert',
|
||||
'sslkey',
|
||||
'sslrootcert',
|
||||
'sslcrl',
|
||||
'sslcrl'
|
||||
)
|
||||
|
||||
|
||||
@@ -260,9 +260,9 @@ class Config(object):
|
||||
if value:
|
||||
ret['log']['loggers'] = value
|
||||
|
||||
def _get_auth(name):
|
||||
def _get_auth(name, params=None):
|
||||
ret = {}
|
||||
for param in AUTH_ALLOWED_PARAMETERS:
|
||||
for param in params or _AUTH_ALLOWED_PARAMETERS[:2]:
|
||||
value = _popenv(name + '_' + param)
|
||||
if value:
|
||||
ret[param] = value
|
||||
@@ -274,7 +274,7 @@ class Config(object):
|
||||
|
||||
authentication = {}
|
||||
for user_type in ('replication', 'superuser', 'rewind'):
|
||||
entry = _get_auth(user_type)
|
||||
entry = _get_auth(user_type, _AUTH_ALLOWED_PARAMETERS)
|
||||
if entry:
|
||||
authentication[user_type] = entry
|
||||
|
||||
@@ -366,7 +366,7 @@ class Config(object):
|
||||
# handle setting additional connection parameters that may be available
|
||||
# in the configuration file, such as SSL connection parameters
|
||||
for name, value in pg_config['authentication'].items():
|
||||
pg_config['authentication'][name] = {n: v for n, v in value.items() if n in AUTH_ALLOWED_PARAMETERS}
|
||||
pg_config['authentication'][name] = {n: v for n, v in value.items() if n in _AUTH_ALLOWED_PARAMETERS}
|
||||
|
||||
# no 'name' in config
|
||||
if 'name' not in config and 'name' in pg_config:
|
||||
|
||||
@@ -161,8 +161,7 @@ class Member(namedtuple('Member', 'index,name,session,data')):
|
||||
if auth and isinstance(auth, dict):
|
||||
ret.update(auth)
|
||||
if 'username' in auth:
|
||||
ret['user'] = auth['username']
|
||||
del ret['username']
|
||||
ret['user'] = ret.pop('username')
|
||||
return ret
|
||||
|
||||
@property
|
||||
|
||||
@@ -77,7 +77,6 @@ class Postgresql(object):
|
||||
|
||||
self.slots_handler = SlotsHandler(self)
|
||||
|
||||
self._pgpass = config.get('pgpass') or os.path.join(os.path.expanduser('~'), 'pgpass')
|
||||
self._callback_executor = CallbackExecutor()
|
||||
self.__cb_called = False
|
||||
self.__cb_pending = None
|
||||
@@ -258,19 +257,6 @@ class Postgresql(object):
|
||||
def data_directory_empty(self):
|
||||
return not os.path.exists(self._data_dir) or os.listdir(self._data_dir) == []
|
||||
|
||||
def write_pgpass(self, record):
|
||||
if 'user' not in record or 'password' not in record:
|
||||
return os.environ.copy()
|
||||
|
||||
with open(self._pgpass, 'w') as f:
|
||||
if os.name != 'nt':
|
||||
os.fchmod(f.fileno(), 0o600)
|
||||
f.write('{host}:{port}:*:{user}:{password}\n'.format(**record))
|
||||
|
||||
env = os.environ.copy()
|
||||
env['PGPASSFILE'] = self._pgpass
|
||||
return env
|
||||
|
||||
def replica_method_options(self, method):
|
||||
return deepcopy(self.config.get(method, {}))
|
||||
|
||||
@@ -645,12 +631,10 @@ class Postgresql(object):
|
||||
|
||||
@contextmanager
|
||||
def get_replication_connection_cursor(self, host='localhost', port=5432, database=None, **kwargs):
|
||||
replication = self.config.replication
|
||||
extra_kwargs = {k: v for k, v in replication.items()
|
||||
if k not in ('username', 'password', 'connect_timeout', 'options')}
|
||||
with get_connection_cursor(host=host, port=int(port), database=database or self._database, replication=1,
|
||||
user=replication['username'], password=replication.get('password'),
|
||||
connect_timeout=3, options='-c statement_timeout=2000', **extra_kwargs) as cur:
|
||||
conn_kwargs = self.config.replication.copy()
|
||||
conn_kwargs.update(host=host, port=int(port), database=database or self._database, connect_timeout=3,
|
||||
user=conn_kwargs.pop('username'), replication=1, options='-c statement_timeout=2000')
|
||||
with get_connection_cursor(**conn_kwargs) as cur:
|
||||
yield cur
|
||||
|
||||
def get_local_timeline_lsn_from_replication_connection(self):
|
||||
@@ -696,7 +680,7 @@ class Postgresql(object):
|
||||
min_apply_delay = is_remote_master and member.recovery_min_apply_delay
|
||||
archive_cleanup = is_remote_master and member.archive_cleanup_command
|
||||
|
||||
primary_conninfo = self.config.primary_conninfo(member)
|
||||
primary_conninfo = self.config.primary_conninfo_params(member)
|
||||
change_role = self.cb_called and (self.role in ('master', 'demoted') or
|
||||
not {'standby_leader', 'replica'} - {self.role, role})
|
||||
|
||||
|
||||
@@ -135,16 +135,8 @@ class Bootstrap(object):
|
||||
# (pghost empty or the default socket directory) connections coming from the local machine.
|
||||
r['host'] = 'localhost' # set it to localhost to write into pgpass
|
||||
|
||||
if 'user' in r:
|
||||
user = r['user']
|
||||
else:
|
||||
user = ''
|
||||
if 'password' in r:
|
||||
import getpass
|
||||
r.setdefault('user', os.environ.get('PGUSER', getpass.getuser()))
|
||||
|
||||
connstring = uri('postgres', (host, r['port']), r['database'], user)
|
||||
env = self._postgresql.write_pgpass(r) if 'password' in r else None
|
||||
connstring = uri('postgres', (host, r['port']), r['database'], r.get('user'))
|
||||
env = self._postgresql.config.write_pgpass(r) if 'password' in r else None
|
||||
|
||||
try:
|
||||
ret = self._postgresql.cancellable.call(shlex.split(cmd) + [connstring], env=env)
|
||||
@@ -178,7 +170,7 @@ class Bootstrap(object):
|
||||
r = clone_member.conn_kwargs(self._postgresql.config.replication)
|
||||
connstring = uri('postgres', (r['host'], r['port']), r['database'], r['user'])
|
||||
# add the credentials to connect to the replica origin to pgpass.
|
||||
env = self._postgresql.write_pgpass(r)
|
||||
env = self._postgresql.config.write_pgpass(r)
|
||||
else:
|
||||
connstring = ''
|
||||
env = os.environ.copy()
|
||||
|
||||
+127
-55
@@ -154,6 +154,37 @@ def mtime(filename):
|
||||
return None
|
||||
|
||||
|
||||
class ConfigWriter(object):
|
||||
|
||||
def __init__(self, filename):
|
||||
self._filename = filename
|
||||
self._fd = None
|
||||
|
||||
def __enter__(self):
|
||||
self._fd = open(self._filename, 'w')
|
||||
self.writeline('# Do not edit this file manually!\n# It will be overwritten by Patroni!')
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self._fd:
|
||||
self._fd.close()
|
||||
|
||||
def writeline(self, line):
|
||||
self._fd.write(line)
|
||||
self._fd.write('\n')
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.writeline(line)
|
||||
|
||||
@staticmethod
|
||||
def escape(value): # Escape (by doubling) any single quotes or backslashes in given string
|
||||
return re.sub(r'([\'\\])', r'\1\1', str(value))
|
||||
|
||||
def write_param(self, param, value):
|
||||
self.writeline("{0} = '{1}'".format(param, self.escape(value)))
|
||||
|
||||
|
||||
class ConfigHandler(object):
|
||||
|
||||
# List of parameters which must be always passed to postmaster as command line options
|
||||
@@ -187,23 +218,6 @@ class ConfigHandler(object):
|
||||
'wal_log_hints': ('on', lambda _: False, 90400)
|
||||
})
|
||||
|
||||
# A list of keywords that can be found in a conninfo string. Follows what
|
||||
# is acceptable by libpq
|
||||
_CONNINFO_KEYWORDS = (
|
||||
'user',
|
||||
'password',
|
||||
'host',
|
||||
'port',
|
||||
'sslmode',
|
||||
'sslcompression',
|
||||
'sslcert',
|
||||
'sslkey',
|
||||
'sslrootcert',
|
||||
'sslcrl',
|
||||
'application_name',
|
||||
'krbsrvname',
|
||||
)
|
||||
|
||||
_RECOVERY_PARAMETERS = {
|
||||
'archive_cleanup_command',
|
||||
'restore_command',
|
||||
@@ -223,8 +237,6 @@ class ConfigHandler(object):
|
||||
'trigger_file'
|
||||
}
|
||||
|
||||
_CONFIG_WARNING_HEADER = '# Do not edit this file manually!\n# It will be overwritten by Patroni!\n'
|
||||
|
||||
def __init__(self, postgresql, config):
|
||||
self._postgresql = postgresql
|
||||
self._config_dir = os.path.abspath(config.get('config_dir') or postgresql.data_dir)
|
||||
@@ -241,6 +253,9 @@ class ConfigHandler(object):
|
||||
self._standby_signal = os.path.join(postgresql.data_dir, 'standby.signal')
|
||||
self._auto_conf = os.path.join(postgresql.data_dir, 'postgresql.auto.conf')
|
||||
self._auto_conf_mtime = None
|
||||
self._pgpass = config.get('pgpass') or os.path.join(os.path.expanduser('~'), 'pgpass')
|
||||
self._passfile = None
|
||||
self._passfile_mtime = None
|
||||
self._synchronous_standby_names = None
|
||||
self._postmaster_ctime = None
|
||||
self._primary_conninfo = None
|
||||
@@ -300,28 +315,26 @@ class ConfigHandler(object):
|
||||
if 'custom_conf' not in self._config and not os.path.exists(self._postgresql_base_conf):
|
||||
os.rename(self._postgresql_conf, self._postgresql_base_conf)
|
||||
|
||||
with open(self._postgresql_conf, 'w') as f:
|
||||
os.chmod(self._postgresql_conf, stat.S_IWRITE | stat.S_IREAD)
|
||||
f.write(self._CONFIG_WARNING_HEADER)
|
||||
f.write("include '{0}'\n\n".format(self._config.get('custom_conf') or self._postgresql_base_conf_name))
|
||||
with ConfigWriter(self._postgresql_conf) as f:
|
||||
include = self._config.get('custom_conf') or self._postgresql_base_conf_name
|
||||
f.writeline("include '{0}'\n".format(ConfigWriter.escape(include)))
|
||||
for name, value in sorted((configuration or self._server_parameters).items()):
|
||||
if (not self._postgresql.bootstrap.running_custom_bootstrap or name != 'hba_file') \
|
||||
and name not in self._RECOVERY_PARAMETERS:
|
||||
f.write("{0} = '{1}'\n".format(name, value))
|
||||
f.write_param(name, value)
|
||||
# when we are doing custom bootstrap we assume that we don't know superuser password
|
||||
# and in order to be able to change it, we are opening trust access from a certain address
|
||||
# therefore we need to make sure that hba_file is not overriden
|
||||
# after changing superuser password we will "revert" all these "changes"
|
||||
if self._postgresql.bootstrap.running_custom_bootstrap or 'hba_file' not in self._server_parameters:
|
||||
f.write("hba_file = '{0}'\n".format(self._pg_hba_conf.replace('\\', '\\\\')))
|
||||
f.write_param('hba_file', self._pg_hba_conf)
|
||||
if 'ident_file' not in self._server_parameters:
|
||||
f.write("ident_file = '{0}'\n".format(self._pg_ident_conf.replace('\\', '\\\\')))
|
||||
f.write_param('ident_file', self._pg_ident_conf)
|
||||
|
||||
if self._postgresql.major_version >= 120000:
|
||||
if self._recovery_params:
|
||||
f.write('\n# recovery.conf\n')
|
||||
for name, value in sorted(self._recovery_params.items()):
|
||||
f.write("{0} = '{1}'\n".format(name, value))
|
||||
f.writeline('\n# recovery.conf')
|
||||
self._write_recovery_params(f, self._recovery_params)
|
||||
|
||||
if not self._postgresql.bootstrap.keep_existing_recovery_conf:
|
||||
self._sanitize_auto_conf()
|
||||
@@ -349,18 +362,15 @@ class ConfigHandler(object):
|
||||
self.local_replication_address['host'], self.local_replication_address['port'],
|
||||
0, socket.SOCK_STREAM, socket.IPPROTO_TCP)})
|
||||
|
||||
with open(self._pg_hba_conf, 'w') as f:
|
||||
f.write(self._CONFIG_WARNING_HEADER)
|
||||
with ConfigWriter(self._pg_hba_conf) as f:
|
||||
for address, t in addresses.items():
|
||||
f.write((
|
||||
f.writeline((
|
||||
'{0}\treplication\t{1}\t{3}\ttrust\n'
|
||||
'{0}\tall\t{2}\t{3}\ttrust\n'
|
||||
'{0}\tall\t{2}\t{3}\ttrust'
|
||||
).format(t, self.replication['username'], self._superuser.get('username') or 'all', address))
|
||||
elif not self.hba_file and self._config.get('pg_hba'):
|
||||
with open(self._pg_hba_conf, 'w') as f:
|
||||
f.write(self._CONFIG_WARNING_HEADER)
|
||||
for line in self._config['pg_hba']:
|
||||
f.write('{0}\n'.format(line))
|
||||
with ConfigWriter(self._pg_hba_conf) as f:
|
||||
f.writelines(self._config['pg_hba'])
|
||||
return True
|
||||
|
||||
def replace_pg_ident(self):
|
||||
@@ -372,29 +382,44 @@ class ConfigHandler(object):
|
||||
"""
|
||||
|
||||
if not self._server_parameters.get('ident_file') and self._config.get('pg_ident'):
|
||||
with open(self._pg_ident_conf, 'w') as f:
|
||||
f.write(self._CONFIG_WARNING_HEADER)
|
||||
for line in self._config['pg_ident']:
|
||||
f.write('{0}\n'.format(line))
|
||||
with ConfigWriter(self._pg_ident_conf) as f:
|
||||
f.writelines(self._config['pg_ident'])
|
||||
return True
|
||||
|
||||
def primary_conninfo_params(self, member):
|
||||
name = self._postgresql.name
|
||||
if not (member and member.conn_url) or member.name == name:
|
||||
if not (member and member.conn_url) or member.name == self._postgresql.name:
|
||||
return None
|
||||
ret = member.conn_kwargs(self.replication)
|
||||
ret.update(application_name=name, sslmode='prefer')
|
||||
ret['application_name'] = self._postgresql.name
|
||||
ret.setdefault('sslmode', 'prefer')
|
||||
if self._krbsrvname:
|
||||
ret['krbsrvname'] = self._krbsrvname
|
||||
if 'database' in ret:
|
||||
del ret['database']
|
||||
return ret
|
||||
|
||||
def primary_conninfo(self, member):
|
||||
r = self.primary_conninfo_params(member)
|
||||
if not r:
|
||||
return None
|
||||
return ' '.join('{0}={{{0}}}'.format(kw) for kw in self._CONNINFO_KEYWORDS if r.get(kw)).format(**r)
|
||||
def format_dsn(self, params, include_dbname=False):
|
||||
# A list of keywords that can be found in a conninfo string. Follows what is acceptable by libpq
|
||||
keywords = ('user', 'passfile', 'host', 'port', 'sslmode', 'sslcompression', 'sslcert',
|
||||
'sslkey', 'sslrootcert', 'sslcrl', 'application_name', 'krbsrvname')
|
||||
if include_dbname:
|
||||
params['dbname'] = params.get('database') or self._postgresql.database
|
||||
keywords = ('dbname',) + keywords
|
||||
|
||||
def escape(value):
|
||||
return re.sub(r'([\'\\ ])', r'\\\1', str(value))
|
||||
|
||||
return ' '.join('{0}={1}'.format(kw, escape(params[kw])) for kw in keywords if params.get(kw) is not None)
|
||||
|
||||
def _write_recovery_params(self, fd, recovery_params):
|
||||
for name, value in sorted(recovery_params.items()):
|
||||
if name == 'primary_conninfo':
|
||||
if 'password' in value:
|
||||
self.write_pgpass(value)
|
||||
value['passfile'] = self._passfile = self._pgpass
|
||||
self._passfile_mtime = mtime(self._pgpass)
|
||||
value = self.format_dsn(value)
|
||||
fd.write_param(name, value)
|
||||
|
||||
def recovery_conf_exists(self):
|
||||
if self._postgresql.major_version >= 120000:
|
||||
@@ -404,12 +429,13 @@ class ConfigHandler(object):
|
||||
def _read_primary_conninfo(self):
|
||||
pg_conf_mtime = mtime(self._postgresql_conf)
|
||||
auto_conf_mtime = mtime(self._auto_conf)
|
||||
passfile_mtime = mtime(self._passfile) if self._passfile else False
|
||||
postmaster_ctime = self._postgresql.is_running()
|
||||
if postmaster_ctime:
|
||||
postmaster_ctime = postmaster_ctime.create_time()
|
||||
|
||||
if self._postgresql_conf_mtime == pg_conf_mtime and self._auto_conf_mtime == auto_conf_mtime \
|
||||
and self._postmaster_ctime == postmaster_ctime:
|
||||
and self._passfile_mtime == passfile_mtime and self._postmaster_ctime == postmaster_ctime:
|
||||
return None, False
|
||||
|
||||
try:
|
||||
@@ -423,7 +449,8 @@ class ConfigHandler(object):
|
||||
|
||||
def _read_primary_conninfo_pre_v12(self):
|
||||
recovery_conf_mtime = mtime(self._recovery_conf)
|
||||
if recovery_conf_mtime == self._recovery_conf_mtime:
|
||||
passfile_mtime = mtime(self._passfile) if self._passfile else False
|
||||
if recovery_conf_mtime == self._recovery_conf_mtime and passfile_mtime == self._passfile_mtime:
|
||||
return None, False
|
||||
|
||||
primary_conninfo = ''
|
||||
@@ -439,6 +466,27 @@ class ConfigHandler(object):
|
||||
self._recovery_conf_mtime = recovery_conf_mtime
|
||||
return primary_conninfo, True
|
||||
|
||||
def _check_passfile(self, wanted_primary_conninfo):
|
||||
# If there is a passfile in the primary_conninfo try to figure out that
|
||||
# the passfile contains the line allowing connection to the given node.
|
||||
# We assume that the passfile was created by Patroni and therefore doing
|
||||
# the full match and not covering cases when host, port or user are set to '*'
|
||||
passfile = self._primary_conninfo['passfile']
|
||||
passfile_mtime = mtime(passfile)
|
||||
if passfile_mtime:
|
||||
try:
|
||||
with open(passfile) as f:
|
||||
wanted_line = self._pgpass_line(wanted_primary_conninfo).strip()
|
||||
for raw_line in f:
|
||||
if raw_line.strip() == wanted_line:
|
||||
self._primary_conninfo['password'] = wanted_primary_conninfo['password']
|
||||
self._passfile = passfile
|
||||
self._passfile_mtime = passfile_mtime
|
||||
return True
|
||||
except Exception:
|
||||
logger.info('Failed to read %s', passfile)
|
||||
return False
|
||||
|
||||
def check_recovery_conf(self, member): # Name is confusing. In fact it checks the value of primary_conninfo
|
||||
# TODO: recovery.conf could be stale, would be nice to detect that.
|
||||
if self._postgresql.major_version >= 120000:
|
||||
@@ -480,6 +528,10 @@ class ConfigHandler(object):
|
||||
elif not self._primary_conninfo:
|
||||
return False
|
||||
|
||||
if 'passfile' in self._primary_conninfo and 'password' in wanted_primary_conninfo and \
|
||||
'password' not in self._primary_conninfo and not self._check_passfile(wanted_primary_conninfo):
|
||||
return False
|
||||
|
||||
return all(self._primary_conninfo.get(p) == str(v) for p, v in wanted_primary_conninfo.items())
|
||||
|
||||
@staticmethod
|
||||
@@ -487,6 +539,28 @@ class ConfigHandler(object):
|
||||
if os.path.isfile(name) or os.path.islink(name):
|
||||
os.unlink(name)
|
||||
|
||||
@staticmethod
|
||||
def _pgpass_line(record):
|
||||
if 'password' in record:
|
||||
def escape(value):
|
||||
return re.sub(r'([:\\])', r'\\\1', str(value))
|
||||
|
||||
record = {n: escape(record.get(n, '*')) for n in ('host', 'port', 'user', 'password')}
|
||||
return '{host}:{port}:*:{user}:{password}'.format(**record)
|
||||
|
||||
def write_pgpass(self, record):
|
||||
line = self._pgpass_line(record)
|
||||
if not line:
|
||||
return os.environ.copy()
|
||||
|
||||
with open(self._pgpass, 'w') as f:
|
||||
os.chmod(self._pgpass, stat.S_IWRITE | stat.S_IREAD)
|
||||
f.write(line)
|
||||
|
||||
env = os.environ.copy()
|
||||
env['PGPASSFILE'] = self._pgpass
|
||||
return env
|
||||
|
||||
def write_recovery_conf(self, recovery_params):
|
||||
if self._postgresql.major_version >= 120000:
|
||||
if parse_bool(recovery_params.pop('standby_mode', None)):
|
||||
@@ -496,10 +570,9 @@ class ConfigHandler(object):
|
||||
open(self._recovery_signal, 'w').close()
|
||||
self._recovery_params = recovery_params
|
||||
else:
|
||||
with open(self._recovery_conf, 'w') as f:
|
||||
with ConfigWriter(self._recovery_conf) as f:
|
||||
os.chmod(self._recovery_conf, stat.S_IWRITE | stat.S_IREAD)
|
||||
for name, value in recovery_params.items():
|
||||
f.write("{0} = '{1}'\n".format(name, value))
|
||||
self._write_recovery_params(f, recovery_params)
|
||||
|
||||
def remove_recovery_conf(self):
|
||||
for name in (self._recovery_conf, self._standby_signal, self._recovery_signal):
|
||||
@@ -678,7 +751,6 @@ class ConfigHandler(object):
|
||||
self._postgresql.set_pending_restart(pending_restart)
|
||||
self._server_parameters = server_parameters
|
||||
self._adjust_recovery_parameters()
|
||||
self._connect_address = config.get('connect_address')
|
||||
self._krbsrvname = config.get('krbsrvname')
|
||||
|
||||
# for not so obvious connection attempts that may happen outside of pyscopg2
|
||||
|
||||
@@ -145,17 +145,9 @@ class Rewind(object):
|
||||
|
||||
def pg_rewind(self, r):
|
||||
# prepare pg_rewind connection
|
||||
env = self._postgresql.write_pgpass(r)
|
||||
env = self._postgresql.config.write_pgpass(r)
|
||||
env['PGOPTIONS'] = '-c statement_timeout=0'
|
||||
dsn_attrs = [
|
||||
('user', r.get('user')),
|
||||
('host', r.get('host')),
|
||||
('port', r.get('port')),
|
||||
('dbname', r.get('database') or self._postgresql.database),
|
||||
('sslmode', 'prefer'),
|
||||
('sslcompression', '1'),
|
||||
]
|
||||
dsn = " ".join("{0}={1}".format(k, v) for k, v in dsn_attrs if v is not None)
|
||||
dsn = self._postgresql.config.format_dsn(r, True)
|
||||
logger.info('running pg_rewind from %s', dsn)
|
||||
try:
|
||||
return self._postgresql.cancellable.call([self._postgresql.pgcommand('pg_rewind'), '-D',
|
||||
|
||||
+1
-2
@@ -5,7 +5,6 @@ import shutil
|
||||
import unittest
|
||||
|
||||
from mock import Mock, patch
|
||||
from tempfile import gettempdir
|
||||
|
||||
import psycopg2
|
||||
import requests
|
||||
@@ -183,7 +182,7 @@ class PostgresInit(unittest.TestCase):
|
||||
data_dir = 'data/test0'
|
||||
self.p = Postgresql({'name': 'postgresql0', 'scope': 'batman', 'data_dir': data_dir,
|
||||
'config_dir': data_dir, 'retry_timeout': 10,
|
||||
'krbsrvname': 'postgres', 'pgpass': os.path.join(gettempdir(), 'pgpass0'),
|
||||
'krbsrvname': 'postgres', 'pgpass': os.path.join(data_dir, 'pgpass0'),
|
||||
'listen': '127.0.0.2, 127.0.0.3:5432', 'connect_address': '127.0.0.2:5432',
|
||||
'authentication': {'superuser': {'username': 'foo', 'password': 'test'},
|
||||
'replication': {'username': '', 'password': 'rep-pass'}},
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ def run_async(self, func, args=()):
|
||||
@patch.object(Postgresql, 'controldata', Mock(return_value={'Database system identifier': SYSID}))
|
||||
@patch.object(SlotsHandler, 'sync_replication_slots', Mock())
|
||||
@patch.object(ConfigHandler, 'append_pg_hba', Mock())
|
||||
@patch.object(Postgresql, 'write_pgpass', Mock(return_value={}))
|
||||
@patch.object(ConfigHandler, 'write_pgpass', Mock(return_value={}))
|
||||
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())
|
||||
@patch.object(Postgresql, 'query', Mock())
|
||||
@patch.object(Postgresql, 'checkpoint', Mock())
|
||||
|
||||
+16
-13
@@ -186,10 +186,11 @@ class TestPostgresql(BaseTestPostgresql):
|
||||
self.assertFalse(self.p.restart())
|
||||
self.assertEqual(self.p.state, 'restart failed (restarting)')
|
||||
|
||||
@patch('os.chmod', Mock())
|
||||
@patch.object(builtins, 'open', MagicMock())
|
||||
def test_write_pgpass(self):
|
||||
self.p.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
|
||||
self.p.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
|
||||
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
|
||||
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
|
||||
|
||||
def test_checkpoint(self):
|
||||
with patch.object(MockCursor, 'fetchone', Mock(return_value=(True, ))):
|
||||
@@ -199,29 +200,31 @@ class TestPostgresql(BaseTestPostgresql):
|
||||
self.assertEqual(self.p.checkpoint(), 'not accessible or not healty')
|
||||
|
||||
@patch('patroni.postgresql.config.mtime', mock_mtime)
|
||||
@patch.object(MockCursor, 'fetchone', Mock(side_effect=[('foo=bar',), ('',), ('',), ('a=b',)]))
|
||||
def test_check_recovery_conf(self):
|
||||
@patch.object(MockCursor, 'fetchone')
|
||||
def test_check_recovery_conf(self, mock_fetchone):
|
||||
mock_fetchone.side_effect = [('foo=bar',), ('',), ('foo',), ('host=1 passfile=' + self.p.config._pgpass,)]
|
||||
conninfo = {'host': '1', 'password': 'bar'}
|
||||
for version in (120000, 100000):
|
||||
with patch.object(Postgresql, 'major_version', PropertyMock(return_value=version)):
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': 'foo'})
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'sslmode': 'prefer'}})
|
||||
self.assertFalse(self.p.config.check_recovery_conf(None))
|
||||
self.p.config.write_recovery_conf({'primary_conninfo': 'foo'})
|
||||
self.p.config.write_recovery_conf({'primary_conninfo': {'sslmode': 'prefer'}})
|
||||
self.p.config.write_postgresql_conf()
|
||||
self.assertFalse(self.p.config.check_recovery_conf(None))
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on'})
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
with patch('patroni.postgresql.config.ConfigHandler.primary_conninfo_params',
|
||||
Mock(return_value={'a': 'b'})):
|
||||
Mock(return_value=conninfo.copy())):
|
||||
self.assertFalse(self.p.config.check_recovery_conf(None))
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': 'a=b'})
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': conninfo.copy()})
|
||||
self.p.config.check_recovery_conf(None)
|
||||
|
||||
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=120000))
|
||||
@patch.object(Postgresql, 'is_running', MockPostmaster)
|
||||
@patch.object(MockPostmaster, 'create_time', Mock(return_value=1234567), create=True)
|
||||
@patch.object(MockCursor, 'fetchone', Mock(return_value=('',)))
|
||||
def test__read_primary_conninfo(self):
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on'})
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'password': 'foo'}})
|
||||
self.p.config.write_postgresql_conf()
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
@@ -231,9 +234,9 @@ class TestPostgresql(BaseTestPostgresql):
|
||||
|
||||
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=100000))
|
||||
def test__read_primary_conninfo_pre_v12(self):
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on'})
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
self.assertTrue(self.p.config.check_recovery_conf(None))
|
||||
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'password': 'foo'}})
|
||||
self.assertFalse(self.p.config.check_recovery_conf(None))
|
||||
self.assertFalse(self.p.config.check_recovery_conf(None))
|
||||
|
||||
def test_write_postgresql_and_sanitize_auto_conf(self):
|
||||
read_data = 'primary_conninfo = foo\nfoo = bar\n'
|
||||
|
||||
Reference in New Issue
Block a user