Compare commits

...
4 Commits
Author SHA1 Message Date
Alexander KukushkinandOleksii Kliukin 25aa49b240 Run one manual failover test via rest API instead of patronictl
and bump Patroni version
2017-07-31 11:18:01 +02:00
Alexander KukushkinandGitHub 322aa45e09 BUGFIX: patronictl edit-config didn't worked with zookeeper (#492)
When updating config key we should use `ClusterConfig.index` instead of
`ClusterConfig.modify_index`. The second one should be used by Patroni
internally to check that key was really changed, because when key is
deleted and recreated it's version always starts from the same value: 0

In addition to that use patronictl instead of http PATCH in some of
acceptance tests to change cluster config.

Fixes https://github.com/zalando/patroni/issues/491
2017-07-31 11:07:00 +02:00
Oleksii Kliukin 9f9acb6a55 Fix a watchdog unit test on OS X. 2017-07-28 16:45:29 +02:00
Alexander KukushkinandGitHub f8b3703d6e Bugfix: failover via API didn't work due to change in _MemberStatus (#489)
Originally fetch_nodes_statuses was returning a tuple, later it was
wrapped into namedtuple _MemberStatus and recently _MemberStatus was
extened with watchdog_failed field, but api.py was still relying on
usual tuple and checking failover limitations on it's own instead of
calling `failover_limitation` method.
2017-07-28 15:38:55 +02:00
7 changed files with 18 additions and 15 deletions
+5 -5
View File
@@ -34,9 +34,9 @@ Scenario: check local configuration reload
Then I receive a response code 202 Then I receive a response code 202
Scenario: check dynamic configuration change via DCS Scenario: check dynamic configuration change via DCS
Given I issue a PATCH request to http://127.0.0.1:8008/config with {"ttl": 10, "loop_wait": 2, "postgresql": {"parameters": {"max_connections": 101}}} Given I run patronictl.py edit-config -s 'ttl=10' -s 'loop_wait=2' -p 'max_connections=101' --force batman
Then I receive a response code 200 Then I receive a response returncode 0
And I receive a response loop_wait 2 And I receive a response output "+loop_wait: 2"
And Response on GET http://127.0.0.1:8008/patroni contains pending_restart after 11 seconds And Response on GET http://127.0.0.1:8008/patroni contains pending_restart after 11 seconds
When I issue a GET request to http://127.0.0.1:8008/config When I issue a GET request to http://127.0.0.1:8008/config
Then I receive a response code 200 Then I receive a response code 200
@@ -65,8 +65,8 @@ Scenario: check API requests for the primary-replica pair in the pause mode
Then postgres1 role is the secondary after 15 seconds Then postgres1 role is the secondary after 15 seconds
Scenario: check the failover via the API in the pause mode Scenario: check the failover via the API in the pause mode
Given I run patronictl.py failover batman --master postgres0 --candidate postgres1 --force Given I issue a POST request to http://127.0.0.1:8008/failover with {"leader": "postgres0", "candidate": "postgres1"}
Then I receive a response returncode 0 Then I receive a response code 200
And postgres1 is a leader after 5 seconds And postgres1 is a leader after 5 seconds
And postgres1 role is the primary after 10 seconds And postgres1 role is the primary after 10 seconds
And postgres0 role is the secondary after 10 seconds And postgres0 role is the secondary after 10 seconds
+2 -2
View File
@@ -300,8 +300,8 @@ class RestApiHandler(BaseHTTPRequestHandler):
members = [m for m in cluster.members if m.name != cluster.leader.name and m.api_url] members = [m for m in cluster.members if m.name != cluster.leader.name and m.api_url]
if not members: if not members:
return 'failover is not possible: cluster does not have members except leader' return 'failover is not possible: cluster does not have members except leader'
for _, reachable, _, _, tags in self.server.patroni.ha.fetch_nodes_statuses(members): for st in self.server.patroni.ha.fetch_nodes_statuses(members):
if reachable and not tags.get('nofailover', False): if st.failover_limitation() is None:
return None return None
return 'failover is not possible: no good candidates have been found' return 'failover is not possible: no good candidates have been found'
+5 -5
View File
@@ -903,14 +903,14 @@ def apply_config_changes(before_editing, data, kvpairs):
if prefix == ('postgresql', 'parameters'): if prefix == ('postgresql', 'parameters'):
path = ['.'.join(path)] path = ['.'.join(path)]
key = path[0]
if len(path) == 1: if len(path) == 1:
if value is None: if value is None:
config.pop(path[0], None) config.pop(key, None)
else: else:
config[path[0]] = value config[key] = value
else: else:
key = path[0] if not isinstance(config.get(key), dict):
if key not in config:
config[key] = {} config[key] = {}
set_path_value(config[key], path[1:], value, prefix + (key,)) set_path_value(config[key], path[1:], value, prefix + (key,))
if config[key] == {}: if config[key] == {}:
@@ -1017,7 +1017,7 @@ def edit_config(obj, cluster_name, force, quiet, kvpairs, pgkvpairs, apply_filen
return return
if force or click.confirm('Apply these changes?'): if force or click.confirm('Apply these changes?'):
if not dcs.set_config_value(json.dumps(changed_data), cluster.config.modify_index): if not dcs.set_config_value(json.dumps(changed_data), cluster.config.index):
raise PatroniCtlException("Config modification aborted due to concurrent changes") raise PatroniCtlException("Config modification aborted due to concurrent changes")
click.echo("Configuration changed") click.echo("Configuration changed")
+1
View File
@@ -26,6 +26,7 @@ class _MemberStatus(namedtuple('_MemberStatus', 'member,reachable,in_recovery,wa
in_recovery - `!True` if pg_is_in_recovery() == true in_recovery - `!True` if pg_is_in_recovery() == true
wal_position - value of `replayed_location` or `location` from JSON, dependin on its role. wal_position - value of `replayed_location` or `location` from JSON, dependin on its role.
tags - dictionary with values of different tags (i.e. nofailover) tags - dictionary with values of different tags (i.e. nofailover)
watchdog_failed - indicates that watchdog is required by configuration but not available or failed
""" """
@classmethod @classmethod
def from_api_response(cls, member, json): def from_api_response(cls, member, json):
+1 -1
View File
@@ -1 +1 @@
__version__ = '1.3' __version__ = '1.3.2'
+3 -2
View File
@@ -6,6 +6,7 @@ import unittest
from mock import Mock, patch from mock import Mock, patch
from patroni.api import RestApiHandler, RestApiServer from patroni.api import RestApiHandler, RestApiServer
from patroni.dcs import ClusterConfig, Member from patroni.dcs import ClusterConfig, Member
from patroni.ha import _MemberStatus
from patroni.utils import tzutc from patroni.utils import tzutc
from six import BytesIO as IO from six import BytesIO as IO
from six.moves import BaseHTTPServer from six.moves import BaseHTTPServer
@@ -38,7 +39,7 @@ class MockPostgresql(object):
class MockWatchdog(object): class MockWatchdog(object):
is_healthy = True is_healthy = False
class MockHa(object): class MockHa(object):
@@ -64,7 +65,7 @@ class MockHa(object):
@staticmethod @staticmethod
def fetch_nodes_statuses(members): def fetch_nodes_statuses(members):
return [[None, True, None, None, {}]] return [_MemberStatus(None, True, None, None, {}, False)]
@staticmethod @staticmethod
def schedule_future_restart(data): def schedule_future_restart(data):
+1
View File
@@ -135,6 +135,7 @@ class TestWatchdog(unittest.TestCase):
self.assertIsNone(wd.disable()) self.assertIsNone(wd.disable())
self.assertIsNone(wd.keepalive()) self.assertIsNone(wd.keepalive())
@patch('platform.system', Mock(return_value='Linux'))
def test_config_reload(self): def test_config_reload(self):
watchdog = Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'required'}}) watchdog = Watchdog({'ttl': 30, 'loop_wait': 15, 'watchdog': {'mode': 'required'}})
self.assertTrue(watchdog.activate()) self.assertTrue(watchdog.activate())