Acceptance tests for patronictl

Call patronictl.py when it's possible instead of doing REST API calls.
This commit is contained in:
Alexander Kukushkin
2016-06-16 15:06:18 +02:00
parent 17f317665f
commit fcde17583c
3 changed files with 78 additions and 53 deletions
+16 -14
View File
@@ -118,6 +118,7 @@ class PatroniController(AbstractController):
with open(patroni_config_name) as f:
config = yaml.safe_load(f)
config.pop('etcd')
host = config['postgresql']['listen'].split(':')[0]
@@ -139,16 +140,6 @@ class PatroniController(AbstractController):
if tags:
config['tags'] = tags
if dcs != 'etcd':
dcs_config = config.pop('etcd')
dcs_config.pop('host')
if dcs == 'exhibitor':
dcs_config.update({'hosts': ['127.0.0.1'], 'port': 8181})
elif dcs == 'zookeeper':
dcs_config['hosts'] = ['127.0.0.1:2181']
config[dcs] = dcs_config
with open(patroni_config_path, 'w') as f:
yaml.safe_dump(config, f, default_flow_style=False)
@@ -220,6 +211,7 @@ class ConsulController(AbstractDcsController):
def __init__(self, output_dir):
super(ConsulController, self).__init__('consul', tempfile.mkdtemp(), output_dir)
os.environ['PATRONI_CONSUL_HOST'] = 'localhost:8500'
self._client = consul.Consul()
def _start(self):
@@ -252,6 +244,7 @@ class EtcdController(AbstractDcsController):
def __init__(self, output_dir):
super(EtcdController, self).__init__('etcd', tempfile.mkdtemp(), output_dir)
os.environ['PATRONI_ETCD_HOST'] = 'localhost:4001'
self._client = etcd.Client()
def _start(self):
@@ -287,8 +280,10 @@ class ZooKeeperController(AbstractDcsController):
""" handles all zookeeper related tasks, used for the tests setup and cleanup """
def __init__(self, output_dir):
def __init__(self, output_dir, export_env=True):
super(ZooKeeperController, self).__init__('zookeeper', None, output_dir)
if export_env:
os.environ['PATRONI_ZOOKEEPER_HOSTS'] = 'localhost:2181'
self._client = kazoo.client.KazooClient()
def _start(self):
@@ -321,10 +316,17 @@ class ZooKeeperController(AbstractDcsController):
return False
class ExhibitorController(ZooKeeperController):
def __init__(self, output_dir):
super(ExhibitorController, self).__init__(output_dir, False)
os.environ.update({'PATRONI_EXHIBITOR_HOSTS': 'localhost', 'PATRONI_EXHIBITOR_PORT': '8181'})
class PatroniPoolController(object):
KNOWN_DCS = {'consul': ConsulController, 'etcd': EtcdController,
'zookeeper': ZooKeeperController, 'exhibitor': ZooKeeperController}
'zookeeper': ZooKeeperController, 'exhibitor': ExhibitorController}
def __init__(self):
self._dcs = None
@@ -376,8 +378,8 @@ class PatroniPoolController(object):
@property
def dcs(self):
if self._dcs is None:
self._dcs = os.environ.get('DCS', 'etcd')
assert self._dcs in self.KNOWN_DCS, 'Unsupported dcs: ' + self.dcs
self._dcs = os.environ.pop('DCS', 'etcd')
assert self._dcs in self.KNOWN_DCS, 'Unsupported dcs: ' + self._dcs
return self._dcs
+39 -34
View File
@@ -10,9 +10,12 @@ Scenario: check API requests on a stand-alone server
And I receive a response role master
When I issue a GET request to http://127.0.0.1:8008/replica
Then I receive a response code 503
When I issue an empty POST request to http://127.0.0.1:8008/reinitialize
Then I receive a response code 503
And I receive a response text "I am the leader, can not reinitialize"
When I run patronictl.py reinit batman postgres0 --force
Then I receive a response returncode 0
And I receive a response output "reinitialize failed for member postgres0, status code=503, (I am the leader, can not reinitialize)"
When I run patronictl.py failover batman --master postgres0 --force
Then I receive a response returncode 1
And I receive a response output "Error: No candidates found to failover to"
When I issue a POST request to http://127.0.0.1:8008/failover with {"leader": "postgres0"}
Then I receive a response code 500
And I receive a response text failover is not possible: cluster does not have members except leader
@@ -23,24 +26,24 @@ Scenario: check API requests on a stand-alone server
And I receive a response text "No values given for required parameters leader and candidate"
Scenario: check local configuration reload
Given I issue an empty POST request to http://127.0.0.1:8008/reload
Then I receive a response code 200
And I receive a response text nothing changed
When I add tag new_tag new_value to postgres0 config
And I issue an empty POST request to http://127.0.0.1:8008/reload
Then I receive a response code 202
Given I issue an empty POST request to http://127.0.0.1:8008/reload
Then I receive a response code 200
And I receive a response text nothing changed
When I add tag new_tag new_value to postgres0 config
And I issue an empty POST request to http://127.0.0.1:8008/reload
Then I receive a response code 202
Scenario: check dynamic configuration change via DCS
Given I issue a PATCH request to http://127.0.0.1:8008/config with {"ttl": 20, "loop_wait": 1, "postgresql": {"parameters": {"max_connections": 101}}}
Then I receive a response code 200
And I receive a response loop_wait 1
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
Then I receive a response code 200
And I receive a response loop_wait 1
When I issue a GET request to http://127.0.0.1:8008/patroni
Then I receive a response code 200
And I receive a response tags {'tag': 'new_value'}
Given I issue a PATCH request to http://127.0.0.1:8008/config with {"ttl": 20, "loop_wait": 1, "postgresql": {"parameters": {"max_connections": 101}}}
Then I receive a response code 200
And I receive a response loop_wait 1
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
Then I receive a response code 200
And I receive a response loop_wait 1
When I issue a GET request to http://127.0.0.1:8008/patroni
Then I receive a response code 200
And I receive a response tags {'tag': 'new_value'}
Scenario: check API requests for the primary-replica pair
Given I start postgres1
@@ -49,26 +52,28 @@ Scenario: check API requests for the primary-replica pair
Then I receive a response code 200
And I receive a response state running
And I receive a response role replica
When I issue an empty POST request to http://127.0.0.1:8009/reinitialize
Then I receive a response code 200
When I issue an empty POST request to http://127.0.0.1:8008/restart
Then I receive a response code 200
And postgres0 role is the primary after 5 seconds
When I sleep for 10 seconds
Then postgres1 role is the secondary after 15 seconds
When I run patronictl.py reinit batman postgres1 --force
Then I receive a response returncode 0
And I receive a response output "Succesful reinitialize on member postgres1"
When I run patronictl.py restart batman postgres0 --force
Then I receive a response returncode 0
And I receive a response output "Succesful restart on member postgres0"
And postgres0 role is the primary after 5 seconds
When I sleep for 10 seconds
Then postgres1 role is the secondary after 15 seconds
Scenario: check the failover via the API
Given I issue a POST request to http://127.0.0.1:8008/failover with {"leader": "postgres0", "candidate": "postgres1"}
Then I receive a response code 200
Given I run patronictl.py failover batman --master postgres0 --candidate postgres1 --force
Then I receive a response returncode 0
And postgres1 is a leader after 5 seconds
And postgres1 role is the primary after 5 seconds
And postgres0 role is the secondary after 10 seconds
And postgres1 role is the primary after 5 seconds
And postgres0 role is the secondary after 10 seconds
And replication works from postgres1 to postgres0 after 20 seconds
Scenario: check the scheduled failover
Given I issue a scheduled failover at http://127.0.0.1:8009 from postgres1 to postgres0 in 1 seconds
Then I receive a response code 202
Given I issue a scheduled failover from postgres1 to postgres0 in 1 seconds
Then I receive a response returncode 0
And postgres0 is a leader after 20 seconds
And postgres0 role is the primary after 5 seconds
And postgres1 role is the secondary after 10 seconds
And postgres0 role is the primary after 5 seconds
And postgres1 role is the secondary after 10 seconds
And replication works from postgres0 to postgres1 after 25 seconds
+23 -5
View File
@@ -2,6 +2,8 @@ import json
import parse
import pytz
import requests
import shlex
import subprocess
import time
import yaml
@@ -84,23 +86,39 @@ def do_request(context, request_method, url, data):
_set_response(context, r)
@step('I run {cmd}')
def do_run(context, cmd):
cmd = ['coverage', 'run', '--source=patroni', '-p'] + shlex.split(cmd)
try:
response = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
context.status_code = 0
except subprocess.CalledProcessError as e:
response = e.output
context.status_code = e.returncode
context.response = response.strip()
@then('I receive a response {component:w} {data}')
def check_response(context, component, data):
if component == 'code':
assert context.status_code == int(data),\
"status code {0} != {1}, response: {2}".format(context.status_code, int(data), context.response)
"status code {0} != {1}, response: {2}".format(context.status_code, data, context.response)
elif component == 'returncode':
assert context.status_code == int(data), "return code {0} != {1}".format(context.status_code, data)
elif component == 'text':
assert context.response == data.strip('"'), "response {0} does not contain {1}".format(context.response, data)
elif component == 'output':
assert data.strip('"') in context.response, "response {0} does not contain {1}".format(context.response, data)
else:
assert component in context.response, "{0} is not part of the response".format(component)
assert str(context.response[component]) == str(data), "{0} does not contain {1}".format(component, data)
@step('I issue a scheduled failover at {at_url:url} from {from_host:w} to {to_host:w} in {in_seconds:d} seconds')
def scheduled_failover(context, at_url, from_host, to_host, in_seconds):
@step('I issue a scheduled failover from {from_host:w} to {to_host:w} in {in_seconds:d} seconds')
def scheduled_failover(context, from_host, to_host, in_seconds):
context.execute_steps(u"""
Given I issue a POST request to {0}/failover with {{"leader": "{1}", "candidate": "{2}", "scheduled_at": "{3}"}}
""".format(at_url, from_host, to_host, datetime.now(pytz.utc) + timedelta(seconds=int(in_seconds))))
Given I run patronictl.py failover batman --master {0} --candidate {1} --scheduled "{2}" --force
""".format(from_host, to_host, datetime.now(pytz.utc) + timedelta(seconds=int(in_seconds))))
@step('I add tag {tag:w} {value:w} to {pg_name:w} config')