mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Merge branch 'master' of github.com:CyberDem0n/governor into features/refactoring
Conflicts: helpers/postgresql.py
This commit is contained in:
@@ -96,7 +96,6 @@ def main():
|
||||
governor.run()
|
||||
finally:
|
||||
governor.postgresql.stop()
|
||||
governor.etcd.delete_member(governor.postgresql.name)
|
||||
governor.etcd.delete_leader(governor.postgresql.name)
|
||||
|
||||
|
||||
|
||||
+9
-5
@@ -9,13 +9,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
Member = namedtuple('Member', 'hostname,address,ttl')
|
||||
Cluster = namedtuple('Cluster', 'leader,last_leader_operation,members')
|
||||
|
||||
|
||||
class Cluster(namedtuple('Cluster', 'leader,last_leader_operation,members')):
|
||||
|
||||
def is_unlocked(self):
|
||||
return not (self.leader and self.leader.hostname)
|
||||
|
||||
|
||||
class Etcd:
|
||||
|
||||
def __init__(self, config):
|
||||
self.ttl = config['ttl']
|
||||
self.member_ttl = config.get('member_ttl', 3600)
|
||||
self.base_client_url = 'http://{host}/v2/keys/service/{scope}'.format(**config)
|
||||
self.postgres_cluster = None
|
||||
|
||||
@@ -115,14 +121,12 @@ class Etcd:
|
||||
def current_leader(self):
|
||||
try:
|
||||
cluster = self.get_cluster()
|
||||
if not cluster.leader or not cluster.leader.address:
|
||||
return None
|
||||
return cluster.leader
|
||||
return None if cluster.is_unlocked() else cluster.leader
|
||||
except:
|
||||
raise CurrentLeaderError("Etcd is not responding properly")
|
||||
|
||||
def touch_member(self, member, connection_string):
|
||||
return self.put_client_path('/members/' + member, value=connection_string, ttl=self.ttl)
|
||||
return self.put_client_path('/members/' + member, value=connection_string, ttl=self.member_ttl)
|
||||
|
||||
def take_leader(self, value):
|
||||
return self.put_client_path('/leader', value=value, ttl=self.ttl)
|
||||
|
||||
+1
-4
@@ -22,9 +22,6 @@ class Ha:
|
||||
def update_lock(self):
|
||||
return self.etcd.update_leader(self.state_handler)
|
||||
|
||||
def is_unlocked(self):
|
||||
return not (self.cluster.leader and self.cluster.leader.hostname)
|
||||
|
||||
def has_lock(self):
|
||||
lock_owner = self.cluster.leader and self.cluster.leader.hostname
|
||||
logger.info('Lock owner: %s; I am %s', lock_owner, self.state_handler.name)
|
||||
@@ -48,7 +45,7 @@ class Ha:
|
||||
logging.info('started as readonly because i had the session lock')
|
||||
self.load_cluster_from_etcd()
|
||||
|
||||
if self.is_unlocked():
|
||||
if self.cluster.is_unlocked():
|
||||
if self.state_handler.is_healthiest_node(self.cluster):
|
||||
if self.acquire_lock():
|
||||
if not self.state_handler.is_leader():
|
||||
|
||||
@@ -54,8 +54,9 @@ class Postgresql:
|
||||
|
||||
self.config = config
|
||||
|
||||
connect_address = config.get('connect_address', config['listen']) or config['listen']
|
||||
self.connection_string = 'postgres://{username}:{password}@{connect_address}/postgres'.format(
|
||||
connect_address=self.config['connect_address'], **self.replication)
|
||||
connect_address=connect_address, **self.replication)
|
||||
|
||||
self.conn = None
|
||||
self.cursor_holder = None
|
||||
@@ -271,11 +272,12 @@ class Postgresql:
|
||||
|
||||
def write_pg_hba(self):
|
||||
with open(os.path.join(self.data_dir, 'pg_hba.conf'), 'a') as f:
|
||||
f.write('host replication {username} {network} md5'.format(**self.replication))
|
||||
f.write('\nhost replication {username} {network} md5\n'.format(**self.replication))
|
||||
# allow TCP connections from the host's own address
|
||||
f.write("\nhost postgres postgres samehost trust\n")
|
||||
# allow TCP connections from the rest of the world with a password, prefer ssl
|
||||
f.write("\nhostssl all all 0.0.0.0/0 md5\n")
|
||||
if self.config['parameters'].get('ssl', 'off').lower() == 'on':
|
||||
f.write("\nhostssl all all 0.0.0.0/0 md5\n")
|
||||
f.write("\nhost all all 0.0.0.0/0 md5\n")
|
||||
|
||||
@staticmethod
|
||||
|
||||
+12
-7
@@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from helpers.etcd import Etcd
|
||||
from helpers.etcd import Cluster, Etcd
|
||||
from helpers.ha import Ha
|
||||
from test_etcd import requests_get, requests_put, requests_delete
|
||||
|
||||
@@ -50,6 +50,10 @@ class MockPostgresql:
|
||||
return 0
|
||||
|
||||
|
||||
def nop(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class TestHa(unittest.TestCase):
|
||||
|
||||
def __init__(self, method_name='runTest'):
|
||||
@@ -63,59 +67,60 @@ class TestHa(unittest.TestCase):
|
||||
self.p = MockPostgresql()
|
||||
self.e = Etcd({'ttl': 30, 'host': 'remotehost', 'scope': 'test'})
|
||||
self.ha = Ha(self.p, self.e)
|
||||
self.ha.cluster = Cluster(None, None, [])
|
||||
self.ha.load_cluster_from_etcd = nop
|
||||
|
||||
def test_start_as_slave(self):
|
||||
self.p.is_healthy = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'started as a secondary')
|
||||
|
||||
def test_start_as_readonly(self):
|
||||
self.ha.cluster.is_unlocked = false
|
||||
self.p.is_leader = self.p.is_healthy = false
|
||||
self.ha.has_lock = true
|
||||
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
|
||||
|
||||
def test_acquire_lock_as_master(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.assertEquals(self.ha.run_cycle(), 'acquired session lock as a leader')
|
||||
|
||||
def test_promoted_by_acquiring_lock(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.p.is_leader = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock')
|
||||
|
||||
def test_demote_after_failing_to_obtain_lock(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.ha.acquire_lock = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'demoted self due after trying and failing to obtain lock')
|
||||
|
||||
def test_follow_new_leader_after_failing_to_obtain_lock(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.ha.acquire_lock = false
|
||||
self.p.is_leader = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'following new leader after trying and failing to obtain lock')
|
||||
|
||||
def test_demote_because_not_healthiest(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.p.is_healthiest_node = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'demoting self because i am not the healthiest node')
|
||||
|
||||
def test_follow_new_leader_because_not_healthiest(self):
|
||||
self.ha.is_unlocked = true
|
||||
self.p.is_healthiest_node = false
|
||||
self.p.is_leader = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node')
|
||||
|
||||
def test_promote_because_have_lock(self):
|
||||
self.ha.cluster.is_unlocked = false
|
||||
self.ha.has_lock = true
|
||||
self.p.is_leader = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'promoted self to leader because i had the session lock')
|
||||
|
||||
def test_leader_with_lock(self):
|
||||
self.ha.cluster.is_unlocked = false
|
||||
self.ha.has_lock = true
|
||||
self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock')
|
||||
|
||||
def test_demote_because_not_having_lock(self):
|
||||
self.ha.cluster.is_unlocked = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'demoting self because i do not have the lock and i was a leader')
|
||||
|
||||
def test_follow_the_leader(self):
|
||||
self.ha.cluster.is_unlocked = false
|
||||
self.p.is_leader = false
|
||||
self.assertEquals(self.ha.run_cycle(), 'no action. i am a secondary and i am following a leader')
|
||||
|
||||
Reference in New Issue
Block a user