Skip to content

Commit

Permalink
Add behavioral tests for static primary feature
Browse files Browse the repository at this point in the history
  • Loading branch information
thedodd committed Apr 28, 2022
1 parent 0d7b7ba commit 0e2c15a
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 10 deletions.
7 changes: 7 additions & 0 deletions features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,13 @@ def start(self, name, max_wait_limit=40, custom_config=None):
self._output_dir, custom_config)
self._processes[name].start(max_wait_limit)

def start_with_expected_failure(self, name, max_wait_limit=40, custom_config=None):
try:
self.start(name, max_wait_limit, custom_config)
assert False, 'expected startup to fail'
except:
pass

def __getattr__(self, func):
if func not in ['stop', 'query', 'write_label', 'read_label', 'check_role_has_changed_to',
'add_tag_to_config', 'get_watchdog', 'patroni_hang', 'backup']:
Expand Down
20 changes: 20 additions & 0 deletions features/static_primary.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Feature: static primary
We should check that static primary behavior is safe

Scenario: check static primary config in dcs blocks replica from starting
Given I start postgres0 as static primary
Then postgres0 is a leader after 10 seconds
And there is a non empty initialize key in DCS after 15 seconds
When I issue a PATCH request to http://127.0.0.1:8008/config with {"ttl": 20, "loop_wait": 2, "synchronous_mode": true}
Then I receive a response code 200
When I start postgres1 with a configured static primary will not boot after 20 seconds
And I start postgres2 with a configured static primary will not boot after 20 seconds
And "sync" key not in DCS after waiting 20 seconds
And "members/postgres1" key not in DCS after waiting 10 seconds
And "members/postgres2" key not in DCS after waiting 10 seconds

Scenario: check removing static primary config from dcs allows replica startup
Given I issue a PATCH request to http://127.0.0.1:8008/config with {"static_primary": null}
Then "sync" key in DCS has leader=postgres0 after 20 seconds
And "members/postgres1" key in DCS has state=running after 10 seconds
And "members/postgres2" key in DCS has state=running after 10 seconds
25 changes: 25 additions & 0 deletions features/steps/static_primary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import patroni.psycopg as pg

from behave import step, then
from time import sleep, time


@step('I start {name:w} as static primary')
def start_patroni_with_static_primary(context, name):
return context.pctl.start(name, custom_config={'bootstrap': {'dcs': {'static_primary': name}}})


@step('I start {name:w} with a configured static primary will not boot after {time_limit:d} seconds')
def start_patroni_as_replica_with_static_primary(context, name, time_limit):
return context.pctl.start_with_expected_failure(name, max_wait_limit=time_limit)


@step('"{name}" key not in DCS after waiting {time_limit:d} seconds')
def check_member_not_present(context, name, time_limit):
sleep(time_limit)
try:
json.loads(context.dcs_ctl.query(name))
assert False, "found value under DCS key {} after {} seconds".format(name, time_limit)
except Exception:
return
6 changes: 1 addition & 5 deletions patroni/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def _build_environment_configuration():
def _popenv(name):
return os.environ.pop(PATRONI_ENV_PREFIX + name.upper(), None)

for param in ('name', 'namespace', 'scope', 'static_primary'):
for param in ('name', 'namespace', 'scope'):
value = _popenv(param)
if value:
ret[param] = value
Expand Down Expand Up @@ -429,10 +429,6 @@ def _build_effective_configuration(self, dynamic_configuration, local_configurat
if 'name' not in config and 'name' in pg_config:
config['name'] = pg_config['name']

# if 'static_primary' not in config and 'static_primary' in local_configuration
if 'static_primary' in local_configuration:
config['static_primary'] = local_configuration['static_primary']

updated_fields = (
'name',
'scope',
Expand Down
6 changes: 3 additions & 3 deletions patroni/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ def assert_(condition, message="Wrong value"):

schema = Schema({
"name": str,
Optional("static_primary"): str,
"scope": str,
"restapi": {
"listen": validate_host_port_listen,
Expand All @@ -324,8 +323,9 @@ def assert_(condition, message="Wrong value"):
Optional("ttl"): int,
Optional("loop_wait"): int,
Optional("retry_timeout"): int,
Optional("maximum_lag_on_failover"): int
},
Optional("maximum_lag_on_failover"): int,
Optional("static_primary"): str
},
"pg_hba": [str],
"initdb": [Or(str, dict)]
},
Expand Down
10 changes: 8 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def test_reload_local_configuration(self):
'PATRONI_LOGLEVEL': 'ERROR',
'PATRONI_LOG_LOGGERS': 'patroni.postmaster: WARNING, urllib3: DEBUG',
'PATRONI_LOG_FILE_NUM': '5',
'PATRONI_STATIC_PRIMARY': 'postgres0',
'PATRONI_RESTAPI_USERNAME': 'username',
'PATRONI_RESTAPI_PASSWORD': 'password',
'PATRONI_RESTAPI_LISTEN': '0.0.0.0:8008',
Expand Down Expand Up @@ -82,7 +81,7 @@ def test_reload_local_configuration(self):
@patch('shutil.move', Mock(return_value=None))
@patch('json.dump', Mock())
def test_save_cache(self):
self.config.set_dynamic_configuration({'ttl': 30, 'postgresql': {'foo': 'bar'}})
self.config.set_dynamic_configuration({'ttl': 30, 'static_primary': 'baz', 'postgresql': {'foo': 'bar'}})
with patch('os.fdopen', Mock(side_effect=IOError)):
self.config.save_cache()
with patch('os.fdopen', MagicMock()):
Expand All @@ -100,6 +99,13 @@ def test_standby_cluster_parameters(self):
for name, value in dynamic_configuration['standby_cluster'].items():
self.assertEqual(self.config['standby_cluster'][name], value)

def test_static_primary_parameter(self):
dynamic_configuration = {
'static_primary': 'foobar'
}
self.config.set_dynamic_configuration(dynamic_configuration)
self.assertEqual(self.config['static_primary'], 'foobar')

@patch('os.path.exists', Mock(return_value=True))
@patch('os.path.isfile', Mock(side_effect=lambda fname: fname != 'postgres0'))
@patch('os.path.isdir', Mock(return_value=True))
Expand Down

0 comments on commit 0e2c15a

Please sign in to comment.