mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Make it possible to run pg_rewind without superuser on pg11+ (#1035)
* expose the current patroni version in DCS * expose `checkpoint_after_promote` flag in DCS as an indicator that pg_rewind could be safely executed * other nodes will wait until this flag is set instead of connecting as superuser and issuing the CHECKPOINT * define `postgresql.authention.rewind` with credentials for pg_rewind in patroni configuration files. * create user for pg_rewind if postgres is 11+ * grant execute on functions required for pg_rewind to rewind user
This commit is contained in:
@@ -137,6 +137,9 @@ PostgreSQL
|
|||||||
- **replication**:
|
- **replication**:
|
||||||
- **username**: replication username; the user will be created during initialization. Replicas will use this user to access master via streaming replication
|
- **username**: replication username; the user will be created during initialization. Replicas will use this user to access master via streaming replication
|
||||||
- **password**: replication password; the user will be created during initialization.
|
- **password**: replication password; the user will be created during initialization.
|
||||||
|
- **rewind**:
|
||||||
|
- **username**: name for the user for ``pg_rewind``; the user will be created during initialization of postgres 11+ and all necessary `permissions <https://paquier.xyz/postgresql-2/postgres-11-superuser-rewind/>`__ will be granted.
|
||||||
|
- **password**: password for the user for ``pg_rewind``; the user will be created during initialization.
|
||||||
- **callbacks**: callback scripts to run on certain actions. Patroni will pass the action, role and cluster name. (See scripts/aws.py as an example of how to write them.)
|
- **callbacks**: callback scripts to run on certain actions. Patroni will pass the action, role and cluster name. (See scripts/aws.py as an example of how to write them.)
|
||||||
- **on\_reload**: run this script when configuration reload is triggered.
|
- **on\_reload**: run this script when configuration reload is triggered.
|
||||||
- **on\_restart**: run this script when the postgres restarts (without changing role).
|
- **on\_restart**: run this script when the postgres restarts (without changing role).
|
||||||
|
|||||||
@@ -239,6 +239,19 @@ class Leader(namedtuple('Leader', 'index,session,member')):
|
|||||||
def timeline(self):
|
def timeline(self):
|
||||||
return self.member.data.get('timeline')
|
return self.member.data.get('timeline')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def checkpoint_after_promote(self):
|
||||||
|
"""
|
||||||
|
>>> Leader(1, '', Member.from_node(1, '', '', '{"version":"z"}')).checkpoint_after_promote
|
||||||
|
"""
|
||||||
|
version = self.member.data.get('version')
|
||||||
|
if version:
|
||||||
|
try:
|
||||||
|
if tuple(map(int, version.split('.'))) >= (1, 5, 6):
|
||||||
|
return bool(self.member.data.get('checkpoint_after_promote'))
|
||||||
|
except Exception:
|
||||||
|
logger.debug('Failed to parse Patroni version %s', version)
|
||||||
|
|
||||||
|
|
||||||
class Failover(namedtuple('Failover', 'index,leader,candidate,scheduled_at')):
|
class Failover(namedtuple('Failover', 'index,leader,candidate,scheduled_at')):
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,10 @@ class ZooKeeper(AbstractDCS):
|
|||||||
cluster = self.cluster
|
cluster = self.cluster
|
||||||
member = cluster and cluster.get_member(self._name, fallback_to_leader=False)
|
member = cluster and cluster.get_member(self._name, fallback_to_leader=False)
|
||||||
encoded_data = json.dumps(data, separators=(',', ':')).encode('utf-8')
|
encoded_data = json.dumps(data, separators=(',', ':')).encode('utf-8')
|
||||||
if member and self._client.client_id is not None and member.session != self._client.client_id[0]:
|
if member and (self._client.client_id is not None and member.session != self._client.client_id[0] or
|
||||||
|
not (deep_compare(member.data.get('tags', {}), data.get('tags', {})) and
|
||||||
|
member.data.get('version') == data.get('version') and
|
||||||
|
member.data.get('checkpoint_after_promote') == data.get('checkpoint_after_promote'))):
|
||||||
try:
|
try:
|
||||||
self._client.delete_async(self.member_path).get(timeout=1)
|
self._client.delete_async(self.member_path).get(timeout=1)
|
||||||
except NoNodeError:
|
except NoNodeError:
|
||||||
|
|||||||
+7
-1
@@ -174,12 +174,15 @@ class Ha(object):
|
|||||||
'conn_url': self.state_handler.connection_string,
|
'conn_url': self.state_handler.connection_string,
|
||||||
'api_url': self.patroni.api.connection_string,
|
'api_url': self.patroni.api.connection_string,
|
||||||
'state': self.state_handler.state,
|
'state': self.state_handler.state,
|
||||||
'role': self.state_handler.role
|
'role': self.state_handler.role,
|
||||||
|
'version': self.patroni.version
|
||||||
}
|
}
|
||||||
|
|
||||||
# following two lines are mainly necessary for consul, to avoid creation of master service
|
# following two lines are mainly necessary for consul, to avoid creation of master service
|
||||||
if data['role'] == 'master' and not self.is_leader():
|
if data['role'] == 'master' and not self.is_leader():
|
||||||
data['role'] = 'promoted'
|
data['role'] = 'promoted'
|
||||||
|
if self.is_leader():
|
||||||
|
data['checkpoint_after_promote'] = self.state_handler.checkpoint_after_promote()
|
||||||
tags = self.get_effective_tags()
|
tags = self.get_effective_tags()
|
||||||
if tags:
|
if tags:
|
||||||
data['tags'] = tags
|
data['tags'] = tags
|
||||||
@@ -904,6 +907,9 @@ class Ha(object):
|
|||||||
if msg is not None:
|
if msg is not None:
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
# check if the node is ready to be used by pg_rewind
|
||||||
|
self.state_handler.check_for_checkpoint_after_promote()
|
||||||
|
|
||||||
if self.is_standby_cluster():
|
if self.is_standby_cluster():
|
||||||
# in case of standby cluster we don't really need to
|
# in case of standby cluster we don't really need to
|
||||||
# enforce anything, since the leader is not a master.
|
# enforce anything, since the leader is not a master.
|
||||||
|
|||||||
+42
-9
@@ -38,7 +38,8 @@ STATE_NO_RESPONSE = 'not responding'
|
|||||||
STATE_UNKNOWN = 'unknown'
|
STATE_UNKNOWN = 'unknown'
|
||||||
|
|
||||||
STOP_POLLING_INTERVAL = 1
|
STOP_POLLING_INTERVAL = 1
|
||||||
REWIND_STATUS = type('Enum', (), {'INITIAL': 0, 'CHECK': 1, 'NEED': 2, 'NOT_NEED': 3, 'SUCCESS': 4, 'FAILED': 5})
|
REWIND_STATUS = type('Enum', (), {'INITIAL': 0, 'CHECKPOINT': 1, 'CHECK': 2, 'NEED': 3,
|
||||||
|
'NOT_NEED': 4, 'SUCCESS': 5, 'FAILED': 6})
|
||||||
sync_standby_name_re = re.compile(r'^[A-Za-z_][A-Za-z_0-9\$]*$')
|
sync_standby_name_re = re.compile(r'^[A-Za-z_][A-Za-z_0-9\$]*$')
|
||||||
|
|
||||||
cluster_info_query = ("SELECT CASE WHEN pg_catalog.pg_is_in_recovery() THEN 0 "
|
cluster_info_query = ("SELECT CASE WHEN pg_catalog.pg_is_in_recovery() THEN 0 "
|
||||||
@@ -202,6 +203,11 @@ class Postgresql(object):
|
|||||||
def _replication(self):
|
def _replication(self):
|
||||||
return self.config['authentication']['replication']
|
return self.config['authentication']['replication']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _rewind_credentials(self):
|
||||||
|
return self.config['authentication'].get('rewind', self._superuser) \
|
||||||
|
if self._major_version >= 110000 else self._superuser
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def callback(self):
|
def callback(self):
|
||||||
return self.config.get('callbacks') or {}
|
return self.config.get('callbacks') or {}
|
||||||
@@ -1055,6 +1061,18 @@ class Postgresql(object):
|
|||||||
self.call_nowait(ACTION_ON_RELOAD)
|
self.call_nowait(ACTION_ON_RELOAD)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def check_for_checkpoint_after_promote(self):
|
||||||
|
if self._rewind_state == REWIND_STATUS.INITIAL and self.is_leader():
|
||||||
|
try:
|
||||||
|
timeline = int(self.controldata().get("Latest checkpoint's TimeLineID"))
|
||||||
|
if self.get_master_timeline() == timeline:
|
||||||
|
self._rewind_state = REWIND_STATUS.CHECKPOINT
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
logger.exception('Failed to parse timeline from pg_controldata output')
|
||||||
|
|
||||||
|
def checkpoint_after_promote(self):
|
||||||
|
return self._rewind_state == REWIND_STATUS.CHECKPOINT
|
||||||
|
|
||||||
def check_for_startup(self):
|
def check_for_startup(self):
|
||||||
"""Checks PostgreSQL status and returns if PostgreSQL is in the middle of startup."""
|
"""Checks PostgreSQL status and returns if PostgreSQL is in the middle of startup."""
|
||||||
return self.is_starting() and not self.check_startup_state_changed()
|
return self.is_starting() and not self.check_startup_state_changed()
|
||||||
@@ -1361,7 +1379,7 @@ class Postgresql(object):
|
|||||||
if leader.member.data.get('role') != 'master':
|
if leader.member.data.get('role') != 'master':
|
||||||
return
|
return
|
||||||
# standby cluster
|
# standby cluster
|
||||||
elif not self.check_leader_is_not_in_recovery(**leader.conn_kwargs(self._superuser)):
|
elif not self.check_leader_is_not_in_recovery(**leader.conn_kwargs(self._replication)):
|
||||||
return
|
return
|
||||||
|
|
||||||
history = need_rewind = None
|
history = need_rewind = None
|
||||||
@@ -1425,14 +1443,21 @@ class Postgresql(object):
|
|||||||
return logger.warning('Can not run pg_rewind because postgres is still running')
|
return logger.warning('Can not run pg_rewind because postgres is still running')
|
||||||
|
|
||||||
# prepare pg_rewind connection
|
# prepare pg_rewind connection
|
||||||
r = leader.conn_kwargs(self._superuser)
|
r = leader.conn_kwargs(self._rewind_credentials)
|
||||||
|
|
||||||
# first make sure that we are really trying to rewind
|
# 1. make sure that we are really trying to rewind from the master
|
||||||
# from the master and run a checkpoint on it in order to
|
# 2. make sure that pg_control contains the new timeline by:
|
||||||
# make it store the new timeline ([email protected])
|
# running a checkpoint or
|
||||||
leader_status = self.checkpoint(r)
|
# waiting until Patroni on the master will expose checkpoint_after_promote=True
|
||||||
if leader_status:
|
checkpoint_status = leader.checkpoint_after_promote if isinstance(leader, Leader) else None
|
||||||
return logger.warning('Can not use %s for rewind: %s', leader.name, leader_status)
|
if checkpoint_status is None: # master still runs the old Patroni
|
||||||
|
leader_status = self.checkpoint(leader.conn_kwargs(self._superuser))
|
||||||
|
if leader_status:
|
||||||
|
return logger.warning('Can not use %s for rewind: %s', leader.name, leader_status)
|
||||||
|
elif not checkpoint_status:
|
||||||
|
return logger.info('Waiting for checkpoint on %s before rewind', leader.name)
|
||||||
|
elif not self.check_leader_is_not_in_recovery(**r):
|
||||||
|
return
|
||||||
|
|
||||||
if self.pg_rewind(r):
|
if self.pg_rewind(r):
|
||||||
self._rewind_state = REWIND_STATUS.SUCCESS
|
self._rewind_state = REWIND_STATUS.SUCCESS
|
||||||
@@ -1733,6 +1758,14 @@ END;$$""".format(name, ' '.join(options))
|
|||||||
if task.result:
|
if task.result:
|
||||||
self.create_or_update_role(self._replication['username'],
|
self.create_or_update_role(self._replication['username'],
|
||||||
self._replication.get('password'), ['REPLICATION'])
|
self._replication.get('password'), ['REPLICATION'])
|
||||||
|
|
||||||
|
if self._major_version >= 110000 and 'rewind' in self.config['authentication']:
|
||||||
|
rewind = self.config['authentication']['rewind']
|
||||||
|
self.create_or_update_role(rewind['username'], rewind.get('password'), [])
|
||||||
|
for f in ('pg_ls_dir(text, boolean, boolean)', 'pg_stat_file(text, boolean)',
|
||||||
|
'pg_read_binary_file(text)', 'pg_read_binary_file(text, bigint, bigint, boolean)'):
|
||||||
|
self.query('GRANT EXECUTE ON function pg_catalog.{0} TO "{1}"'.format(f, rewind['username']))
|
||||||
|
|
||||||
for name, value in (config.get('users') or {}).items():
|
for name, value in (config.get('users') or {}).items():
|
||||||
if name not in (self._superuser.get('username'), self._replication['username']):
|
if name not in (self._superuser.get('username'), self._replication['username']):
|
||||||
self.create_or_update_role(name, value.get('password'), value.get('options', []))
|
self.create_or_update_role(name, value.get('password'), value.get('options', []))
|
||||||
|
|||||||
+8
-6
@@ -74,12 +74,6 @@ bootstrap:
|
|||||||
- createdb
|
- createdb
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
# Fully qualified kerberos ticket file for the running user
|
|
||||||
# same as KRB5CCNAME used by the GSS
|
|
||||||
# krb_server_keyfile: /var/spool/keytabs/postgres
|
|
||||||
|
|
||||||
# Server side kerberos spn
|
|
||||||
# krbsrvname: postgres
|
|
||||||
listen: 127.0.0.1:5432
|
listen: 127.0.0.1:5432
|
||||||
connect_address: 127.0.0.1:5432
|
connect_address: 127.0.0.1:5432
|
||||||
data_dir: data/postgresql0
|
data_dir: data/postgresql0
|
||||||
@@ -93,7 +87,15 @@ postgresql:
|
|||||||
superuser:
|
superuser:
|
||||||
username: postgres
|
username: postgres
|
||||||
password: zalando
|
password: zalando
|
||||||
|
rewind: # Has no effect on postgres 10 and lower
|
||||||
|
username: rewind_user
|
||||||
|
password: rewind_password
|
||||||
|
# Server side kerberos spn
|
||||||
|
# krbsrvname: postgres
|
||||||
parameters:
|
parameters:
|
||||||
|
# Fully qualified kerberos ticket file for the running user
|
||||||
|
# same as KRB5CCNAME used by the GSS
|
||||||
|
# krb_server_keyfile: /var/spool/keytabs/postgres
|
||||||
unix_socket_directories: '.'
|
unix_socket_directories: '.'
|
||||||
|
|
||||||
#watchdog:
|
#watchdog:
|
||||||
|
|||||||
+8
-6
@@ -68,12 +68,6 @@ bootstrap:
|
|||||||
- createdb
|
- createdb
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
# Fully qualified kerberos ticket file for the running user
|
|
||||||
# same as KRB5CCNAME used by the GSS
|
|
||||||
# krb_server_keyfile: /var/spool/keytabs/postgres
|
|
||||||
|
|
||||||
# Server side kerberos spn
|
|
||||||
# krbsrvname: postgres
|
|
||||||
listen: 127.0.0.1:5433
|
listen: 127.0.0.1:5433
|
||||||
connect_address: 127.0.0.1:5433
|
connect_address: 127.0.0.1:5433
|
||||||
data_dir: data/postgresql1
|
data_dir: data/postgresql1
|
||||||
@@ -87,7 +81,15 @@ postgresql:
|
|||||||
superuser:
|
superuser:
|
||||||
username: postgres
|
username: postgres
|
||||||
password: zalando
|
password: zalando
|
||||||
|
rewind: # Has no effect on postgres 10 and lower
|
||||||
|
username: rewind_user
|
||||||
|
password: rewind_password
|
||||||
|
# Server side kerberos spn
|
||||||
|
# krbsrvname: postgres
|
||||||
parameters:
|
parameters:
|
||||||
|
# Fully qualified kerberos ticket file for the running user
|
||||||
|
# same as KRB5CCNAME used by the GSS
|
||||||
|
# krb_server_keyfile: /var/spool/keytabs/postgres
|
||||||
unix_socket_directories: '.'
|
unix_socket_directories: '.'
|
||||||
basebackup:
|
basebackup:
|
||||||
- verbose
|
- verbose
|
||||||
|
|||||||
+8
-6
@@ -65,12 +65,6 @@ bootstrap:
|
|||||||
- createdb
|
- createdb
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
# Fully qualified kerberos ticket file for the running user
|
|
||||||
# same as KRB5CCNAME used by the GSS
|
|
||||||
# krb_server_keyfile: /var/spool/keytabs/postgres
|
|
||||||
|
|
||||||
# Server side kerberos spn
|
|
||||||
# krbsrvname: postgres
|
|
||||||
listen: 127.0.0.1:5434
|
listen: 127.0.0.1:5434
|
||||||
connect_address: 127.0.0.1:5434
|
connect_address: 127.0.0.1:5434
|
||||||
data_dir: data/postgresql2
|
data_dir: data/postgresql2
|
||||||
@@ -84,7 +78,15 @@ postgresql:
|
|||||||
superuser:
|
superuser:
|
||||||
username: postgres
|
username: postgres
|
||||||
password: zalando
|
password: zalando
|
||||||
|
rewind: # Has no effect on postgres 10 and lower
|
||||||
|
username: rewind_user
|
||||||
|
password: rewind_password
|
||||||
|
# Server side kerberos spn
|
||||||
|
# krbsrvname: postgres
|
||||||
parameters:
|
parameters:
|
||||||
|
# Fully qualified kerberos ticket file for the running user
|
||||||
|
# same as KRB5CCNAME used by the GSS
|
||||||
|
# krb_server_keyfile: /var/spool/keytabs/postgres
|
||||||
unix_socket_directories: '.'
|
unix_socket_directories: '.'
|
||||||
tags:
|
tags:
|
||||||
nofailover: false
|
nofailover: false
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ zookeeper:
|
|||||||
|
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
self.config.set_dynamic_configuration({'maximum_lag_on_failover': 5})
|
self.config.set_dynamic_configuration({'maximum_lag_on_failover': 5})
|
||||||
|
self.version = '1.5.7'
|
||||||
self.postgresql = p
|
self.postgresql = p
|
||||||
self.dcs = d
|
self.dcs = d
|
||||||
self.api = Mock()
|
self.api = Mock()
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
os.makedirs(self.data_dir)
|
os.makedirs(self.data_dir)
|
||||||
self.p = Postgresql({'name': 'test0', 'scope': 'batman', 'data_dir': self.data_dir,
|
self.p = Postgresql({'name': 'test0', 'scope': 'batman', 'data_dir': self.data_dir,
|
||||||
'config_dir': self.config_dir, 'retry_timeout': 10,
|
'config_dir': self.config_dir, 'retry_timeout': 10,
|
||||||
'pgpass': os.path.join(gettempdir(), 'pgpass0'),
|
'krbsrvname': 'postgres', 'pgpass': os.path.join(gettempdir(), 'pgpass0'),
|
||||||
'listen': '127.0.0.2, 127.0.0.3:5432', 'connect_address': '127.0.0.2:5432',
|
'listen': '127.0.0.2, 127.0.0.3:5432', 'connect_address': '127.0.0.2:5432',
|
||||||
'authentication': {'superuser': {'username': 'test', 'password': 'test'},
|
'authentication': {'superuser': {'username': 'test', 'password': 'test'},
|
||||||
'replication': {'username': 'replicator', 'password': 'rep-pass'}},
|
'replication': {'username': 'replicator', 'password': 'rep-pass'}},
|
||||||
@@ -398,6 +398,14 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
self.p.config['remove_data_directory_on_rewind_failure'] = False
|
self.p.config['remove_data_directory_on_rewind_failure'] = False
|
||||||
self.p.trigger_check_diverged_lsn()
|
self.p.trigger_check_diverged_lsn()
|
||||||
self.p.rewind(self.leader)
|
self.p.rewind(self.leader)
|
||||||
|
|
||||||
|
self.leader.member.data.update(version='1.5.7', checkpoint_after_promote=False)
|
||||||
|
self.assertIsNone(self.p.rewind(self.leader))
|
||||||
|
|
||||||
|
self.leader.member.data['checkpoint_after_promote'] = True
|
||||||
|
with patch.object(Postgresql, 'check_leader_is_not_in_recovery', Mock(return_value=False)):
|
||||||
|
self.assertIsNone(self.p.rewind(self.leader))
|
||||||
|
|
||||||
with patch.object(Postgresql, 'is_running', Mock(return_value=True)):
|
with patch.object(Postgresql, 'is_running', Mock(return_value=True)):
|
||||||
self.p.rewind(self.leader)
|
self.p.rewind(self.leader)
|
||||||
self.p.is_leader = Mock(return_value=False)
|
self.p.is_leader = Mock(return_value=False)
|
||||||
@@ -663,7 +671,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
@patch.object(Postgresql, 'run_bootstrap_post_init', Mock(return_value=True))
|
@patch.object(Postgresql, 'run_bootstrap_post_init', Mock(return_value=True))
|
||||||
@patch.object(Postgresql, '_custom_bootstrap', Mock(return_value=True))
|
@patch.object(Postgresql, '_custom_bootstrap', Mock(return_value=True))
|
||||||
@patch.object(Postgresql, 'start', Mock(return_value=True))
|
@patch.object(Postgresql, 'start', Mock(return_value=True))
|
||||||
@patch.object(Postgresql, 'get_major_version', Mock(return_value=90600))
|
@patch.object(Postgresql, 'get_major_version', Mock(return_value=110000))
|
||||||
def test_post_bootstrap(self):
|
def test_post_bootstrap(self):
|
||||||
config = {'method': 'foo', 'foo': {'command': 'bar'}}
|
config = {'method': 'foo', 'foo': {'command': 'bar'}}
|
||||||
self.p.bootstrap(config)
|
self.p.bootstrap(config)
|
||||||
@@ -686,7 +694,8 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
self.p.bootstrap(config)
|
self.p.bootstrap(config)
|
||||||
self.p.set_state('stopped')
|
self.p.set_state('stopped')
|
||||||
self.p.reload_config({'authentication': {'superuser': {'username': 'p', 'password': 'p'},
|
self.p.reload_config({'authentication': {'superuser': {'username': 'p', 'password': 'p'},
|
||||||
'replication': {'username': 'r', 'password': 'r'}},
|
'replication': {'username': 'r', 'password': 'r'},
|
||||||
|
'rewind': {'username': 'rw', 'password': 'rw'}},
|
||||||
'listen': '*', 'retry_timeout': 10, 'parameters': {'wal_level': '', 'hba_file': 'foo'}})
|
'listen': '*', 'retry_timeout': 10, 'parameters': {'wal_level': '', 'hba_file': 'foo'}})
|
||||||
with patch.object(Postgresql, 'restart', Mock()) as mock_restart:
|
with patch.object(Postgresql, 'restart', Mock()) as mock_restart:
|
||||||
self.p.post_bootstrap({}, task)
|
self.p.post_bootstrap({}, task)
|
||||||
@@ -787,7 +796,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
parameters = self._PARAMETERS.copy()
|
parameters = self._PARAMETERS.copy()
|
||||||
parameters.pop('f.oo')
|
parameters.pop('f.oo')
|
||||||
config = {'pg_hba': [''], 'pg_ident': [''], 'use_unix_socket': True, 'authentication': {},
|
config = {'pg_hba': [''], 'pg_ident': [''], 'use_unix_socket': True, 'authentication': {},
|
||||||
'retry_timeout': 10, 'listen': '*', 'parameters': parameters}
|
'retry_timeout': 10, 'listen': '*', 'krbsrvname': 'postgres', 'parameters': parameters}
|
||||||
self.p.reload_config(config)
|
self.p.reload_config(config)
|
||||||
parameters['b.ar'] = 'bar'
|
parameters['b.ar'] = 'bar'
|
||||||
self.p.reload_config(config)
|
self.p.reload_config(config)
|
||||||
@@ -1040,3 +1049,7 @@ class TestPostgresql(unittest.TestCase):
|
|||||||
self.p.cancel()
|
self.p.cancel()
|
||||||
self.assertFalse(self.p.start())
|
self.assertFalse(self.p.start())
|
||||||
self.assertTrue(self.p.pending_restart)
|
self.assertTrue(self.p.pending_restart)
|
||||||
|
|
||||||
|
@patch.object(Postgresql, 'controldata', Mock(return_value={"Latest checkpoint's TimeLineID": 1}))
|
||||||
|
def test_check_for_checkpoint_after_promote(self):
|
||||||
|
self.p.check_for_checkpoint_after_promote()
|
||||||
|
|||||||
Reference in New Issue
Block a user