diff --git a/features/environment.py b/features/environment.py index 90207ab0..f031f19b 100644 --- a/features/environment.py +++ b/features/environment.py @@ -1,8 +1,8 @@ -import os.path +import os import psycopg2 import requests -import subprocess import shutil +import subprocess import tempfile import time import yaml @@ -50,7 +50,7 @@ class PatroniController(object): return None return content.strip() - def start(self, pg_name, max_wait_limit=15, tags=None): + def start(self, pg_name, max_wait_limit=20, tags=None): if not self._is_running(pg_name): if pg_name in self._processes: del self._processes[pg_name] diff --git a/features/steps/basic_replication.py b/features/steps/basic_replication.py index 26af60e9..364de61f 100644 --- a/features/steps/basic_replication.py +++ b/features/steps/basic_replication.py @@ -4,22 +4,22 @@ from behave import step, then from time import sleep, time -@step('I start {name}') +@step('I start {name:w}') def start_patroni(context, name): return context.pctl.start(name) -@step('I shut down {name}') +@step('I shut down {name:w}') def stop_patroni(context, name): return context.pctl.stop(name) -@step('I kill {name}') +@step('I kill {name:w}') def kill_patroni(context, name): return context.pctl.stop(name, kill=True) -@step('I add the table {table_name} to {pg_name}') +@step('I add the table {table_name:w} to {pg_name:w}') def add_table(context, table_name, pg_name): # parse the configuration file and get the port try: @@ -28,7 +28,7 @@ def add_table(context, table_name, pg_name): assert False, "Error creating table {0} on {1}: {2}".format(table_name, pg_name, e) -@then('Table {table_name} is present on {pg_name} after {max_replication_delay} seconds') +@then('Table {table_name:w} is present on {pg_name:w} after {max_replication_delay:d} seconds') def table_is_present_on(context, table_name, pg_name, max_replication_delay): for _ in range(int(max_replication_delay)): if context.pctl.query(pg_name, "SELECT 1 FROM {0}".format(table_name), fail_ok=True) is not None: @@ -39,15 +39,15 @@ def table_is_present_on(context, table_name, pg_name, max_replication_delay): "Table {0} is not present on {1} after {2} seconds".format(table_name, pg_name, max_replication_delay) -@then('{pg_name} role is the {pg_role} after {max_promotion_timeout} seconds') +@then('{pg_name:w} role is the {pg_role:w} after {max_promotion_timeout:d} seconds') def check_role(context, pg_name, pg_role, max_promotion_timeout): if not context.pctl.check_role_has_changed_to(pg_name, pg_role, timeout=int(max_promotion_timeout)): assert False,\ "{0} role didn't change to {1} after {2} seconds".format(pg_name, pg_role, max_promotion_timeout) -@step('replication works from {master} to {replica} after {time_limit} seconds') -@then('replication works from {master} to {replica} after {time_limit} seconds') +@step('replication works from {master:w} to {replica:w} after {time_limit:d} seconds') +@then('replication works from {master:w} to {replica:w} after {time_limit:d} seconds') def replication_works(context, master, replica, time_limit): context.execute_steps(u""" When I add the table test_{0} to {1} diff --git a/features/steps/cascading_replication.py b/features/steps/cascading_replication.py index 07d8fbd2..59399c97 100644 --- a/features/steps/cascading_replication.py +++ b/features/steps/cascading_replication.py @@ -1,17 +1,17 @@ from behave import step, then -@step('I configure and start {name} with a tag {tag_name} {tag_value}') +@step('I configure and start {name:w} with a tag {tag_name:w} {tag_value:w}') def start_patroni_with_a_name_value_tag(context, name, tag_name, tag_value): return context.pctl.start(name, tags={tag_name: tag_value}) -@then('There is a label with "{content}" in {name} data directory') +@then('There is a label with "{content:w}" in {name:w} data directory') def check_label(context, content, name): label = context.pctl.read_label(name) assert label == content, "{0} is not equal to {1}".format(label, content) -@step('I create label with "{content}" in {name} data directory') +@step('I create label with "{content:w}" in {name:w} data directory') def write_label(context, content, name): context.pctl.write_label(name, content) diff --git a/features/steps/patroni_api.py b/features/steps/patroni_api.py index 309a20af..0f802dee 100644 --- a/features/steps/patroni_api.py +++ b/features/steps/patroni_api.py @@ -1,9 +1,23 @@ -import time +import parse import pytz import requests +import time +from behave import register_type, step, then from datetime import datetime, timedelta -from behave import step, then + + +@parse.with_pattern(r'https?://(?:\w|\.|:|/)+') +def parse_url(text): + return text + + +@parse.with_pattern(r'(?:\w+=(?:\w|\.|:|-|\+|\s)+,?)+') +def parse_data(text): + return text + + +register_type(url=parse_url, data=parse_data) # there is no way we can find out if the node has already @@ -11,8 +25,8 @@ from behave import step, then # just rely on the database availability, since there is # a short gap between the time PostgreSQL becomes available # and Patroni assuming the leader role. -@step('{name} is a leader after {time_limit} seconds') -@then('{name} is a leader after {time_limit} seconds') +@step('{name:w} is a leader after {time_limit:d} seconds') +@then('{name:w} is a leader after {time_limit:d} seconds') def is_a_leader(context, name, time_limit): max_time = time.time() + int(time_limit) while (context.etcd_ctl.query("leader") != name): @@ -21,12 +35,12 @@ def is_a_leader(context, name, time_limit): assert False, "{0} is not a leader in etcd after {1} seconds".format(name, time_limit) -@step('I sleep for {value} seconds') +@step('I sleep for {value:d} seconds') def sleep_for_n_seconds(context, value): time.sleep(int(value)) -@step('I issue a GET request to {url}') +@step('I issue a GET request to {url:url}') def do_get(context, url): try: r = requests.get(url) @@ -41,12 +55,12 @@ def do_get(context, url): context.response = r.content.decode('utf-8') -@step('I issue an empty POST request to {url}') +@step('I issue an empty POST request to {url:url}') def do_post_empty(context, url): do_post(context, url, None) -@step('I issue a POST request to {url} with {data}') +@step('I issue a POST request to {url:url} with {data:data}') def do_post(context, url, data): post_data = {} if data: @@ -68,7 +82,7 @@ def do_post(context, url, data): context.response = r.content.decode('utf-8') -@then('I receive a response {component} {data}') +@then('I receive a response {component:w} {data}') def check_response(context, component, data): if component == 'code': assert context.status_code == int(data),\ @@ -80,8 +94,8 @@ def check_response(context, component, data): assert context.response[component] == data, "{0} does not contain {1}".format(component, data) -@step('I issue a scheduled failover at {at_url} from {from_host} to {to_host} in {in_seconds} seconds') -def scheduld_failover(context, at_url, from_host, to_host, in_seconds): - context.execute_steps(""" +@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): + 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))))