diff --git a/patroni/__init__.py b/patroni/__init__.py index 6406143b..cc008fda 100644 --- a/patroni/__init__.py +++ b/patroni/__init__.py @@ -6,6 +6,7 @@ import yaml from patroni.api import RestApiServer from patroni.etcd import Etcd +from patroni.exceptions import DCSError from patroni.ha import Ha from patroni.postgresql import Postgresql from patroni.utils import setup_signal_handlers, sleep, reap_children @@ -59,21 +60,26 @@ class Patroni: # is data directory empty? if self.postgresql.data_directory_empty(): while True: - # racing to initialize - if self.ha.dcs.initialize(): - try: - self.postgresql.bootstrap() - except: - # bail out and clean the initialize flag. - self.cleanup_on_failed_initialization() - raise - self.ha.dcs.take_leader() - break - else: - leader = self.ha.dcs.current_leader() - if leader and self.postgresql.bootstrap(leader): + try: + cluster = self.ha.dcs.get_cluster() + if not cluster.is_unlocked(): # the leader already exists + if not cluster.initialize: + self.ha.dcs.initialize() + self.postgresql.bootstrap(cluster.leader) break - sleep(5) + # racing to initialize + elif not cluster.initialize and self.ha.dcs.initialize(): + try: + self.postgresql.bootstrap() + except: + # bail out and clean the initialize flag. + self.cleanup_on_failed_initialization() + raise + self.ha.dcs.take_leader() + break + except DCSError: + logger.info('waiting on DCS') + sleep(5) elif self.postgresql.is_running(): self.postgresql.load_replication_slots() diff --git a/tests/test_patroni.py b/tests/test_patroni.py index d5740e9c..317e1e5c 100644 --- a/tests/test_patroni.py +++ b/tests/test_patroni.py @@ -9,7 +9,7 @@ import yaml from mock import Mock, patch from patroni.api import RestApiServer -from patroni.dcs import Cluster, Member +from patroni.dcs import Cluster, Member, Leader from patroni.etcd import Etcd from patroni.exceptions import PostgresException from patroni import Patroni, main @@ -42,6 +42,30 @@ class Mock_BaseServer__is_shut_down: pass +def get_cluster(initialize, leader): + return Cluster(initialize, leader, None, None) + + +def get_cluster_not_initialized_without_leader(): + return get_cluster(None, None) + + +def get_cluster_initialized_without_leader(): + return get_cluster(True, None) + + +def get_cluster_not_initialized_with_leader(): + return get_cluster(False, Leader(0, 0, 0, + Member(0, 'leader', 'postgres://replicator:rep-pass@127.0.0.1:5435/postgres', + None, None, 28))) + + +def get_cluster_initialized_with_leader(): + return get_cluster(True, Leader(0, 0, 0, + Member(0, 'leader', 'postgres://replicator:rep-pass@127.0.0.1:5435/postgres', + None, None, 28))) + + class TestPatroni(unittest.TestCase): def __init__(self, method_name='runTest'): @@ -129,24 +153,31 @@ class TestPatroni(unittest.TestCase): def test_patroni_initialize(self): self.p.ha.dcs.client.write = etcd_write + self.p.ha.dcs.client.read = etcd_read self.p.touch_member = self.touch_member self.p.postgresql.data_directory_empty = true self.p.ha.dcs.initialize = true self.p.postgresql.initialize = true self.p.postgresql.start = true + self.p.ha.dcs.get_cluster = get_cluster_not_initialized_without_leader self.p.initialize() self.p.ha.dcs.initialize = false + self.p.ha.dcs.get_cluster = get_cluster_initialized_with_leader time.sleep = time_sleep self.p.ha.dcs.client.read = etcd_read self.p.initialize() - self.p.ha.dcs.current_leader = nop + self.p.ha.dcs.get_cluster = get_cluster_initialized_without_leader self.assertRaises(SleepException, self.p.initialize) self.p.postgresql.data_directory_empty = false self.p.initialize() + self.p.ha.dcs.get_cluster = get_cluster_not_initialized_with_leader + self.p.postgresql.data_directory_empty = true + self.p.initialize() + def test_schedule_next_run(self): self.p.next_run = time.time() - self.p.nap_time - 1 self.p.schedule_next_run() @@ -156,6 +187,8 @@ class TestPatroni(unittest.TestCase): def test_cleanup_on_initialization(self): self.p.ha.dcs.client.write = etcd_write + self.p.ha.dcs.client.read = etcd_read + self.p.ha.dcs.get_cluster = get_cluster_not_initialized_without_leader self.p.touch_member = self.touch_member self.p.postgresql.data_directory_empty = true self.p.ha.dcs.initialize = true diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index 273d077d..56dbc557 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -6,6 +6,7 @@ import unittest from patroni.dcs import Cluster, Leader, Member from patroni.postgresql import Postgresql +from test_ha import true, false def nop(*args, **kwargs): @@ -217,3 +218,9 @@ class TestPostgresql(unittest.TestCase): self.p.start() self.p.query = self.mock_query self.assertTrue(self.p.stop()) + + def test_move_data_directory(self): + self.p.is_running = is_running + os.rename = nop + os.path.isdir = true + self.p.move_data_directory()