From 2200a4ce8c6122241eae0815c44a5b8952015388 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 21 Oct 2015 15:51:21 +0200 Subject: [PATCH 01/10] Add support for per-member tags. Tags are labels assigned to individual members in order to alter its default behavior, i.e. exclude from the leader election or indicate a possibility to create base backups from the member. This commit only adds support for setting tags in the configuration file, exposes the tags to DCS /member subkey and returns the tags in a response of the API request. At the moment the tag names are not validated, nor they are interpreted in any way. Support for setting tags via the API is also in the scope of further work. --- patroni/__init__.py | 1 + patroni/api.py | 5 +++++ patroni/ha.py | 3 ++- postgres0.yml | 5 +++++ postgres1.yml | 5 +++++ tests/test_api.py | 1 + tests/test_ha.py | 1 + 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/patroni/__init__.py b/patroni/__init__.py index 150334ba..5769a8fc 100644 --- a/patroni/__init__.py +++ b/patroni/__init__.py @@ -18,6 +18,7 @@ class Patroni: def __init__(self, config): self.nap_time = config['loop_wait'] + self.tags = config.get('tags', dict()) self.postgresql = Postgresql(config['postgresql']) self.dcs = self.get_dcs(self.postgresql.name, config) host, port = config['restapi']['listen'].split(':') diff --git a/patroni/api.py b/patroni/api.py index dc83249f..e4071e8e 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -46,6 +46,7 @@ class RestApiHandler(BaseHTTPRequestHandler): path = '/master' if self.path == '/' else self.path response = self.get_postgresql_status() + response.update(self.get_tags()) patroni = self.server.patroni cluster = patroni.dcs.cluster @@ -75,6 +76,7 @@ class RestApiHandler(BaseHTTPRequestHandler): def do_GET_patroni(self): response = self.get_postgresql_status(True) + response.update(self.get_tags()) self.send_response(200) self.send_header('Content-Type', 'application/json') @@ -174,6 +176,9 @@ class RestApiHandler(BaseHTTPRequestHandler): state = 'unknown' if state == 'running' else state return {'state': state} + def get_tags(self): + return {'tags': self.server.patroni.tags} + class RestApiServer(ThreadingMixIn, HTTPServer, Thread): diff --git a/patroni/ha.py b/patroni/ha.py index 0019253d..d8ff4756 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -50,7 +50,8 @@ class Ha: 'conn_url': self.state_handler.connection_string, 'api_url': self.patroni.api.connection_string, 'state': self.state_handler.state, - 'role': self.state_handler.role + 'role': self.state_handler.role, + 'tags': self.patroni.tags } if data['state'] in ['running', 'restarting', 'starting']: try: diff --git a/postgres0.yml b/postgres0.yml index a155b1cd..0aef6d4e 100644 --- a/postgres0.yml +++ b/postgres0.yml @@ -67,3 +67,8 @@ postgresql: max_replication_slots: 5 hot_standby: "on" wal_log_hints: "on" +tags: + nofailover: False + noloadbalance: False + clonefrom: False + replicatefrom: 127.0.0.1 diff --git a/postgres1.yml b/postgres1.yml index 94e33a42..34e55eae 100644 --- a/postgres1.yml +++ b/postgres1.yml @@ -67,3 +67,8 @@ postgresql: max_replication_slots: 5 hot_standby: "on" wal_log_hints: "on" +tags: + nofailover: False + noloadbalance: False + clonefrom: False + replicatefrom: 127.0.0.1 diff --git a/tests/test_api.py b/tests/test_api.py index e4b8f87e..7fdf9568 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -44,6 +44,7 @@ class MockPatroni: postgresql = MockPostgresql() ha = MockHa() dcs = Mock() + tags = {} class MockRequest: diff --git a/tests/test_ha.py b/tests/test_ha.py index a5a816da..d149746c 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -82,6 +82,7 @@ class MockPatroni: self.postgresql = p self.dcs = d self.api = Mock() + self.tags = {} self.api.connection_string = 'http://127.0.0.1:8008' From b7b47ffd7967581e46ae68e99b78bac3ea879c95 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 23 Oct 2015 10:11:38 +0200 Subject: [PATCH 02/10] Add support for the nofailover tag. --- patroni/ha.py | 39 +++++++++++++++++++++++++++------------ tests/test_etcd.py | 2 +- tests/test_ha.py | 10 +++++----- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/patroni/ha.py b/patroni/ha.py index d8ff4756..7a632673 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -140,7 +140,9 @@ class Ha: reachable - `!False` if the node is not reachable or is not responding with correct JSON in_recovery - `!True` if pg_is_in_recovery() == true - xlog_location - value of `replayed_location` or `location` from JSON, dependin on its role.""" + xlog_location - value of `replayed_location` or `location` from JSON, dependin on its role. + tags - dictionary with values of different tags (i.e. nofailover) + """ try: response = requests.get(member.api_url, timeout=2, verify=False) @@ -148,10 +150,11 @@ class Ha: json = response.json() is_master = json['role'] == 'master' xlog_location = json['xlog']['location' if is_master else 'replayed_location'] - return (member, True, not is_master, xlog_location) + tags = json.get('tags', dict()) + return (member, True, not is_master, xlog_location, tags) except: logging.exception('request failed: GET %s', member.api_url) - return (member, False, None, 0) + return (member, False, None, 0, {}) def fetch_nodes_statuses(self, members): pool = ThreadPool(len(members)) @@ -166,16 +169,20 @@ class Ha: if self.state_handler.is_leader(): return True + if self.patroni.tags.get('nofailover') is True: + return False + if check_replication_lag and not self.state_handler.check_replication_lag(self.cluster.last_leader_operation): return False # Too far behind last reported xlog location on master # Prepare list of nodes to run check against - members = [m for m in members if m.name != self.state_handler.name and m.api_url] + members = [m for m in members if m.name != self.state_handler.name + and not m.data.get('tags', {}).get('nofailover', None) and m.api_url] if members: my_xlog_location = self.state_handler.xlog_position() - for member, reachable, in_recovery, xlog_location in self.fetch_nodes_statuses(members): - if reachable: # If the node is unreachable it's not healhy + for member, reachable, in_recovery, xlog_location, tags in self.fetch_nodes_statuses(members): + if reachable and not tags.get('nofailover'): # If the node is unreachable it's not healhy if not in_recovery: logger.warning('Master (%s) is still alive', member.name) return False @@ -187,11 +194,13 @@ class Ha: ret = False members = [m for m in members if m.name != self.state_handler.name and m.api_url] if members: - for member, reachable, in_recovery, xlog_location in self.fetch_nodes_statuses(members): - if reachable: + for member, reachable, in_recovery, xlog_location, tags in self.fetch_nodes_statuses(members): + if reachable and not tags.get('nofailover'): ret = True # TODO: check xlog_location - else: + elif not reachable: logger.info('Member %s is not reachable', member.name) + elif tags.get('nofailover'): + logger.info('Member %s is not allowed to promote', member.name) else: logger.warning('manual failover: members list is empty') return ret @@ -205,12 +214,15 @@ class Ha: # find specific node and check that it is healthy members = [m for m in self.cluster.members if m.name == failover.member] if members: - member, reachable, in_recovery, xlog_location = self.fetch_node_status(members[0]) - if reachable: # node is healthy + member, reachable, in_recovery, xlog_location, tags = self.fetch_node_status(members[0]) + if reachable and not tags.get('nofailover'): # node is healthy logger.info('manual failover: to %s, i am %s', member.name, self.state_handler.name) return False # we wanted to failover to specific member but it is not healthy - logger.warning('manual failover: member %s is unhealthy', member.name) + if not reachable: + logger.warning('manual failover: member %s is unhealthy', member.name) + elif tags.get('nofailover'): + logger.warning('manual failover: member %s is not allowed to promote', member.name) # at this point we should consider all members as a candidates for failover # i.e. we assume that failover.member is None @@ -278,6 +290,9 @@ class Ha: return self.follow_the_leader('demoted self due after trying and failing to obtain lock', 'following new leader after trying and failing to obtain lock') else: + if self.patroni.tags.get('nofailover'): + return self.follow_the_leader('demoting self because I am not allowed to become master', + 'following a different leader because I am not allowed to promote') return self.follow_the_leader('demoting self because i am not the healthiest node', 'following a different leader because i am not the healthiest node') diff --git a/tests/test_etcd.py b/tests/test_etcd.py index 53c054e5..6b9df83f 100644 --- a/tests/test_etcd.py +++ b/tests/test_etcd.py @@ -50,7 +50,7 @@ def requests_get(url, **kwargs): if url.startswith('http://local'): raise requests.exceptions.RequestException() elif ':8011/patroni' in url: - response.content = '{"role": "replica", "xlog": {"replayed_location": 0}}' + response.content = '{"role": "replica", "xlog": {"replayed_location": 0}, "tags": {}}' elif url.endswith('/members'): if url.startswith('http://error'): response.content = '[{}]' diff --git a/tests/test_ha.py b/tests/test_ha.py index d149746c..32248c61 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -275,21 +275,21 @@ class TestHa(unittest.TestCase): self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'leader')) self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') - self.ha.fetch_node_status = lambda e: (e, True, True, 0) # accessible, in_recovery + self.ha.fetch_node_status = lambda e: (e, True, True, 0, {}) # accessible, in_recovery self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, MockPostgresql.name, '')) self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') - self.ha.fetch_node_status = lambda e: (e, False, True, 0) # accessible, in_recovery + self.ha.fetch_node_status = lambda e: (e, False, True, 0, {}) # accessible, in_recovery self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') def test__is_healthiest_node(self): self.assertTrue(self.ha._is_healthiest_node(self.ha.old_cluster.members)) self.p.is_leader = false - self.ha.fetch_node_status = lambda e: (e, True, True, 0) # accessible, in_recovery + self.ha.fetch_node_status = lambda e: (e, True, True, 0, {}) # accessible, in_recovery self.assertTrue(self.ha._is_healthiest_node(self.ha.old_cluster.members)) - self.ha.fetch_node_status = lambda e: (e, True, False, 0) # accessible, not in_recovery + self.ha.fetch_node_status = lambda e: (e, True, False, 0, {}) # accessible, not in_recovery self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) - self.ha.fetch_node_status = lambda e: (e, True, True, 1) # accessible, in_recovery, xlog location ahead + self.ha.fetch_node_status = lambda e: (e, True, True, 1, {}) # accessible, in_recovery, xlog location ahead self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) self.p.check_replication_lag = false self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) From a2736cd2b7d09e025aa0cba4c585c07a31f9e01d Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 10:36:34 +0100 Subject: [PATCH 03/10] Add nofailover property to Patroni and Cluster. Add checks for nofailover tag in is_failover_possible in order to save on API calls for the nodes with this tag set. --- patroni/__init__.py | 4 ++++ patroni/dcs.py | 4 ++++ patroni/ha.py | 7 +++---- tests/test_ha.py | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/patroni/__init__.py b/patroni/__init__.py index 5769a8fc..78d8e619 100644 --- a/patroni/__init__.py +++ b/patroni/__init__.py @@ -26,6 +26,10 @@ class Patroni: self.ha = Ha(self) self.next_run = time.time() + @property + def nofailover(self): + return self.tags.get('nofailover') + @staticmethod def get_dcs(name, config): if 'etcd' in config: diff --git a/patroni/dcs.py b/patroni/dcs.py index 25e44cd1..b1ed5169 100644 --- a/patroni/dcs.py +++ b/patroni/dcs.py @@ -63,6 +63,10 @@ class Member(namedtuple('Member', 'index,name,session,data')): def api_url(self): return self.data.get('api_url', None) + @property + def nofailover(self): + return self.data.get('tags', {}).get('nofailover') + class Leader(namedtuple('Leader', 'index,session,member')): diff --git a/patroni/ha.py b/patroni/ha.py index 7a632673..cb2d8a5d 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -169,15 +169,14 @@ class Ha: if self.state_handler.is_leader(): return True - if self.patroni.tags.get('nofailover') is True: + if self.patroni.nofailover is True: return False if check_replication_lag and not self.state_handler.check_replication_lag(self.cluster.last_leader_operation): return False # Too far behind last reported xlog location on master # Prepare list of nodes to run check against - members = [m for m in members if m.name != self.state_handler.name - and not m.data.get('tags', {}).get('nofailover', None) and m.api_url] + members = [m for m in members if m.name != self.state_handler.name and not m.nofailover and m.api_url] if members: my_xlog_location = self.state_handler.xlog_position() @@ -192,7 +191,7 @@ class Ha: def is_failover_possible(self, members): ret = False - members = [m for m in members if m.name != self.state_handler.name and m.api_url] + members = [m for m in members if m.name != self.state_handler.name and not m.nofailover and m.api_url] if members: for member, reachable, in_recovery, xlog_location, tags in self.fetch_nodes_statuses(members): if reachable and not tags.get('nofailover'): diff --git a/tests/test_ha.py b/tests/test_ha.py index 32248c61..8079fbd0 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -83,6 +83,7 @@ class MockPatroni: self.dcs = d self.api = Mock() self.tags = {} + self.nofailover = None self.api.connection_string = 'http://127.0.0.1:8008' From 2044d4db96eb5dea240ca547ac27dbc0a513bb19 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 14:52:34 +0100 Subject: [PATCH 04/10] Move nofailover checks on the upper level. Instead of checking that nofailover node should not be marked as healthiest in one of the _failover functions, do make it unhealthy in the is_healthiest_node. --- patroni/ha.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/patroni/ha.py b/patroni/ha.py index 3033ffc7..faa9eeec 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -232,7 +232,7 @@ class Ha: if failover.leader: if self.state_handler.name == failover.leader: # I was the leader # exclude me and desired member which is unhealthy (failover.member can be None) - members = [m for m in self.cluster.members if m.name != failover.member] + members = [m for m in self.cluster.members if m.name not in (failover.member, failover.leader)] if self.is_failover_possible(members): # check that there are healthy members return False else: # I was the leader and it looks like currently I am the only healthy member @@ -245,6 +245,13 @@ class Ha: return self._is_healthiest_node(members, check_replication_lag=False) def is_healthiest_node(self): + + if self.state_handler.is_leader(): # leader is always the healthiest + return True + + if self.patroni.nofailover: # nofailover tag makes node always unhealthy + return False + if self.cluster.failover: return self.manual_failover_process_no_leader() From ed5e3e53e2b19525a2178b3b011ac50a297b17e0 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 17:18:45 +0100 Subject: [PATCH 05/10] Use a shortcut to call patroni nofailover attribute. --- patroni/ha.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patroni/ha.py b/patroni/ha.py index faa9eeec..60ed0e96 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -298,7 +298,7 @@ class Ha: return self.follow_the_leader('demoted self due after trying and failing to obtain lock', 'following new leader after trying and failing to obtain lock') else: - if self.patroni.tags.get('nofailover'): + if self.patroni.nofailover: return self.follow_the_leader('demoting self because I am not allowed to become master', 'following a different leader because I am not allowed to promote') return self.follow_the_leader('demoting self because i am not the healthiest node', From 1f9d8c039c9c7317b81aed186afb337f452c3f91 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 17:19:35 +0100 Subject: [PATCH 06/10] Add tests for nofailover. --- tests/test_ha.py | 24 +++++++++++++++++++++++- tests/test_patroni.py | 6 ++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_ha.py b/tests/test_ha.py index aa176559..43d4cded 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -274,6 +274,11 @@ class TestHa(unittest.TestCase): f = Failover(0, MockPostgresql.name, '') self.ha.cluster = get_cluster_initialized_with_leader(f) self.assertEquals(self.ha.run_cycle(), 'manual failover: demoting myself') + self.ha.fetch_node_status = lambda e: (e, True, True, 0, {'nofailover': 'True'}) + self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') + # manual failover from the previous leader to us won't happen if we hold the nofailover flag + self.ha.cluster = get_cluster_initialized_with_leader(Failover(0, 'blabla', MockPostgresql.name)) + self.assertEquals(self.ha.run_cycle(), 'no action. i am the leader with the lock') @patch('requests.get', requests_get) def test_manual_failover_process_no_leader(self): @@ -286,8 +291,22 @@ class TestHa(unittest.TestCase): self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, MockPostgresql.name, '')) self.assertEquals(self.ha.run_cycle(), 'following a different leader because i am not the healthiest node') - self.ha.fetch_node_status = lambda e: (e, False, True, 0, {}) # accessible, in_recovery + self.ha.fetch_node_status = lambda e: (e, False, True, 0, {}) # inaccessible, in_recovery self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') + # set failover flag to True for all members of the cluster + # this should elect the current member, as we are not going to call the API for it. + self.ha.cluster = get_cluster_initialized_without_leader(failover=Failover(0, '', 'other')) + self.ha.fetch_node_status = lambda e: (e, True, True, 0, {'nofailover': 'True'}) # accessible, in_recovery + self.assertEquals(self.ha.run_cycle(), 'promoted self to leader by acquiring session lock') + # same as previous, but set the current member to nofailover. In no case it should be elected as a leader + self.ha.patroni.nofailover = True + self.assertEquals(self.ha.run_cycle(), 'following a different leader because I am not allowed to promote') + + def test_is_healthiest_node(self): + self.ha.state_handler.is_leader = false + self.ha.patroni.nofailover = False + self.ha.fetch_node_status = lambda e: (e, True, True, 0, {}) + self.assertTrue(self.ha.is_healthiest_node()) def test__is_healthiest_node(self): self.assertTrue(self.ha._is_healthiest_node(self.ha.old_cluster.members)) @@ -300,6 +319,9 @@ class TestHa(unittest.TestCase): self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) self.p.check_replication_lag = false self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) + self.ha.patroni.nofailover = True + self.assertFalse(self.ha._is_healthiest_node(self.ha.old_cluster.members)) + self.ha.patroni.nofailover = False @patch('requests.get', requests_get) def test_fetch_node_status(self): diff --git a/tests/test_patroni.py b/tests/test_patroni.py index 52b10d7a..18f5c14b 100644 --- a/tests/test_patroni.py +++ b/tests/test_patroni.py @@ -74,3 +74,9 @@ class TestPatroni(unittest.TestCase): self.p.schedule_next_run() self.p.next_run = time.time() - self.p.nap_time - 1 self.p.schedule_next_run() + + def test_nofailover(self): + self.p.tags['nofailover'] = True + self.assertTrue(self.p.nofailover) + self.p.tags['nofailover'] = None + self.assertFalse(self.p.nofailover) From 685363c5a8827d4d2a380d88bbdce1b7ea2ded22 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 18:57:05 +0100 Subject: [PATCH 07/10] Fix an invocation fo fetch_node_statuses in the API code to account for the tags --- patroni/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patroni/api.py b/patroni/api.py index bb168641..e3cb891b 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -148,8 +148,8 @@ class RestApiHandler(BaseHTTPRequestHandler): members = [m for m in cluster.members if m.name != cluster.leader.name and m.api_url] if not members: return b'failover is not possible: cluster does not have members except leader' - for member, reachable, in_recovery, xlog_location in self.server.patroni.ha.fetch_nodes_statuses(members): - if reachable: + for member, reachable, in_recovery, xlog_location, tags in self.server.patroni.ha.fetch_nodes_statuses(members): + if reachable and not tags.get('nofailover'): return None return b'failover is not possible: no good candidates have been found' From da74aee2cda7ded2c10d2333ae5d734118602617 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 26 Oct 2015 18:57:28 +0100 Subject: [PATCH 08/10] Make sure that nofailover presents the node even from initializing the cluster. --- patroni/ha.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patroni/ha.py b/patroni/ha.py index 60ed0e96..e2548e0e 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -74,7 +74,7 @@ class Ha: self._async_executor.schedule('bootstrap from leader') self._async_executor.run_async(self.copy_backup_from_leader, args=(self.cluster.leader, )) return 'trying to bootstrap from leader' - elif not self.cluster.initialize: # no initialize key + elif not self.cluster.initialize and not self.patroni.nofailover: # no initialize key if self.dcs.initialize(create_new=True): # race for initialization try: self.state_handler.bootstrap() From 8d68130aacc40663238bd7d33fdca24b73031358 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 27 Oct 2015 09:32:09 +0100 Subject: [PATCH 09/10] Fix the test to account for an extra parameter returned by fetch_node_statuses. --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index c28529af..dbc881de 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -40,7 +40,7 @@ class MockHa(Mock): return False def fetch_nodes_statuses(self, members): - return [[None, True, None, None]] + return [[None, True, None, None, {}]] class MockPatroni: From a1a7c9b4c562f95f6ae171b8b5f5fc392ff92f8c Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 27 Oct 2015 09:46:03 +0100 Subject: [PATCH 10/10] Make sure nofailover tag is set to False by default. --- patroni/__init__.py | 2 +- patroni/api.py | 2 +- patroni/dcs.py | 2 +- patroni/ha.py | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/patroni/__init__.py b/patroni/__init__.py index 78d8e619..d17d8752 100644 --- a/patroni/__init__.py +++ b/patroni/__init__.py @@ -28,7 +28,7 @@ class Patroni: @property def nofailover(self): - return self.tags.get('nofailover') + return self.tags.get('nofailover', False) @staticmethod def get_dcs(name, config): diff --git a/patroni/api.py b/patroni/api.py index e3cb891b..45ac0034 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -149,7 +149,7 @@ class RestApiHandler(BaseHTTPRequestHandler): if not members: return b'failover is not possible: cluster does not have members except leader' for member, reachable, in_recovery, xlog_location, tags in self.server.patroni.ha.fetch_nodes_statuses(members): - if reachable and not tags.get('nofailover'): + if reachable and not tags.get('nofailover', False): return None return b'failover is not possible: no good candidates have been found' diff --git a/patroni/dcs.py b/patroni/dcs.py index fff1859f..f640787c 100644 --- a/patroni/dcs.py +++ b/patroni/dcs.py @@ -65,7 +65,7 @@ class Member(namedtuple('Member', 'index,name,session,data')): @property def nofailover(self): - return self.data.get('tags', {}).get('nofailover') + return self.data.get('tags', {}).get('nofailover', False) class Leader(namedtuple('Leader', 'index,session,member')): diff --git a/patroni/ha.py b/patroni/ha.py index e2548e0e..dc5bb14c 100644 --- a/patroni/ha.py +++ b/patroni/ha.py @@ -183,7 +183,7 @@ class Ha: if members: my_xlog_location = self.state_handler.xlog_position() for member, reachable, in_recovery, xlog_location, tags in self.fetch_nodes_statuses(members): - if reachable and not tags.get('nofailover'): # If the node is unreachable it's not healhy + if reachable and not tags.get('nofailover', False): # If the node is unreachable it's not healhy if not in_recovery: logger.warning('Master (%s) is still alive', member.name) return False @@ -196,11 +196,11 @@ class Ha: members = [m for m in members if m.name != self.state_handler.name and not m.nofailover and m.api_url] if members: for member, reachable, in_recovery, xlog_location, tags in self.fetch_nodes_statuses(members): - if reachable and not tags.get('nofailover'): + if reachable and not tags.get('nofailover', False): ret = True # TODO: check xlog_location elif not reachable: logger.info('Member %s is not reachable', member.name) - elif tags.get('nofailover'): + elif tags.get('nofailover', False): logger.info('Member %s is not allowed to promote', member.name) else: logger.warning('manual failover: members list is empty') @@ -216,13 +216,13 @@ class Ha: members = [m for m in self.cluster.members if m.name == failover.member] if members: member, reachable, in_recovery, xlog_location, tags = self.fetch_node_status(members[0]) - if reachable and not tags.get('nofailover'): # node is healthy + if reachable and not tags.get('nofailover', False): # node is healthy logger.info('manual failover: to %s, i am %s', member.name, self.state_handler.name) return False # we wanted to failover to specific member but it is not healthy if not reachable: logger.warning('manual failover: member %s is unhealthy', member.name) - elif tags.get('nofailover'): + elif tags.get('nofailover', False): logger.warning('manual failover: member %s is not allowed to promote', member.name) # at this point we should consider all members as a candidates for failover