mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
First implementation of cloning from the replica.
At the moment we just replace the master with the node at the 'clonefrom' tag if it's present. Master should be available anyway, otherwise, it will not even try to do cloning. Acceptance tests: https://github.com/zalando/patroni/pull/144/commits
This commit is contained in:
@@ -35,6 +35,10 @@ class Patroni(object):
|
||||
def replicatefrom(self):
|
||||
return self.tags.get('replicatefrom')
|
||||
|
||||
@property
|
||||
def clonefrom(self):
|
||||
return self.tags.get('clonefrom')
|
||||
|
||||
@staticmethod
|
||||
def get_dcs(name, config):
|
||||
if 'etcd' in config:
|
||||
|
||||
@@ -72,6 +72,10 @@ class Member(namedtuple('Member', 'index,name,session,data')):
|
||||
def replicatefrom(self):
|
||||
return self.data.get('tags', {}).get('replicatefrom')
|
||||
|
||||
@property
|
||||
def clonefrom(self):
|
||||
return self.data.get('tags', {}).get('clonefrom')
|
||||
|
||||
|
||||
class Leader(namedtuple('Leader', 'index,session,member')):
|
||||
|
||||
@@ -147,6 +151,9 @@ class Cluster(namedtuple('Cluster', 'initialize,leader,last_leader_operation,mem
|
||||
def has_member(self, member_name):
|
||||
return any(m for m in self.members if m.name == member_name)
|
||||
|
||||
def get_member(self, member_name):
|
||||
return ([m for m in self.members if m.name == member_name] or [None])[0]
|
||||
|
||||
|
||||
class AbstractDCS(object):
|
||||
|
||||
|
||||
+3
-1
@@ -76,7 +76,9 @@ class Ha(object):
|
||||
def bootstrap(self):
|
||||
if not self.cluster.is_unlocked(): # cluster already has leader
|
||||
self._async_executor.schedule('bootstrap from leader')
|
||||
self._async_executor.run_async(self.clone, args=(self.cluster.leader, ))
|
||||
clonefrom = self.patroni.clonefrom
|
||||
source = self.cluster.get_member(clonefrom) if self.cluster.has_member(clonefrom) else self.cluster.leader
|
||||
self._async_executor.run_async(self.clone, args=(source,))
|
||||
return 'trying to bootstrap from leader'
|
||||
elif not self.cluster.initialize and not self.patroni.nofailover: # no initialize key
|
||||
if self.dcs.initialize(create_new=True): # race for initialization
|
||||
|
||||
@@ -234,6 +234,7 @@ class Postgresql(object):
|
||||
return env
|
||||
|
||||
def sync_replica(self, leader):
|
||||
# add either the leader's or replica's credentials to pgpass
|
||||
env = self.write_pgpass(parseurl(leader.conn_url)) if leader else os.environ.copy()
|
||||
if self.create_replica(leader, env) == 0:
|
||||
self.delete_trigger_file()
|
||||
@@ -258,23 +259,23 @@ class Postgresql(object):
|
||||
replica_methods = self.config.get('create_replica_method', [])
|
||||
return any(self.replica_method_can_work_without_leader(replica_method) for replica_method in replica_methods)
|
||||
|
||||
def create_replica(self, leader, env):
|
||||
def create_replica(self, source, env):
|
||||
# create the replica according to the replica_method
|
||||
# defined by the user. this is a list, so we need to
|
||||
# loop through all methods the user supplies
|
||||
connstring = leader.conn_url if leader else ""
|
||||
connstring = source.conn_url if source else ""
|
||||
# get list of replica methods from config.
|
||||
# If there is no configuration key, or no value is specified, use basebackup
|
||||
replica_methods = self.config.get('create_replica_method') or ['basebackup']
|
||||
# if we don't have any leader, leave only replica methods that work without it
|
||||
replica_methods = [r for r in replica_methods if self.replica_method_can_work_without_leader(r)] if not leader \
|
||||
# if we don't have any source, leave only replica methods that work without it
|
||||
replica_methods = [r for r in replica_methods if self.replica_method_can_work_without_leader(r)] if not source \
|
||||
else replica_methods
|
||||
# go through them in priority order
|
||||
ret = 1
|
||||
for replica_method in replica_methods:
|
||||
# if the method is basebackup, then use the built-in
|
||||
if replica_method == "basebackup":
|
||||
ret = self.basebackup(leader, env)
|
||||
ret = self.basebackup(source, env)
|
||||
if ret == 0:
|
||||
logger.info("replica has been created using basebackup")
|
||||
# if basebackup succeeds, exit with success
|
||||
@@ -702,7 +703,7 @@ $$""".format(name, options), name, password, password)
|
||||
"""
|
||||
Populate PostgreSQL data directory by doing one of the following:
|
||||
- create with initdb if there is no master.
|
||||
- initialize the replica from an existing master
|
||||
- initialize the replica from an existing master or replica
|
||||
- initialize the replica using the replica creation method that
|
||||
works without the master (i.e. restore from on-disk base backup)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class MockPatroni(object):
|
||||
self.nap_time = 10
|
||||
self.replicatefrom = None
|
||||
self.api.connection_string = 'http://127.0.0.1:8008'
|
||||
self.clonefrom = None
|
||||
|
||||
|
||||
def run_async(func, args=()):
|
||||
|
||||
Reference in New Issue
Block a user