diff --git a/features/cascading_replication.feature b/features/cascading_replication.feature new file mode 100644 index 00000000..31500ae9 --- /dev/null +++ b/features/cascading_replication.feature @@ -0,0 +1,12 @@ +Feature: cascading replication + We should check that patroni can do base backup and streaming from the replica + +Scenario: check a base backup from the replica + Given I start postgres0 + And I start postgres1 + And replication works from postgres0 to postgres1 after 15 seconds + And I create label with "postgres0" in postgres0 data directory + And I create label with "postgres1" in postgres1 data directory + And I configure and start postgres2 with a tag clonefrom postgres1 + Then replication works from postgres0 to postgres2 after 15 seconds + And there is a label with "postgres1" in postgres2 data directory \ No newline at end of file diff --git a/features/cascading_replication.py b/features/cascading_replication.py new file mode 100644 index 00000000..a5733aa7 --- /dev/null +++ b/features/cascading_replication.py @@ -0,0 +1,26 @@ +from lettuce import world, steps + + +@steps +class CascadingReplicationSteps(object): + + def __init__(self, environ): + self.env = environ + + @staticmethod + def start_patroni_with_a_name_value_tag(step, name, tag_name, tag_value): + '''I configure and start (\w+) with a tag (\w+) (\w+)''' + return world.pctl.start(name, tags={tag_name: tag_value}) + + @staticmethod + def check_label(step, content, name): + '''There is a label with "(\w+)" in (\w+) data directory''' + label = world.pctl.read_label(name) + assert label == content, "{0} is not equal to {1}".format(label, content) + + @staticmethod + def write_label(step, content, name): + '''I create label with "(\w+)" in (\w+) data directory''' + world.pctl.write_label(name, content) + +CascadingReplicationSteps(world) diff --git a/features/terrain.py b/features/terrain.py index 19841a19..01450070 100644 --- a/features/terrain.py +++ b/features/terrain.py @@ -35,14 +35,30 @@ class PatroniController(object): self._patroni_path = cwd return self._patroni_path - def start(self, pg_name, max_wait_limit=15): + def data_dir(self, pg_name): + return os.path.join(self.patroni_path, 'data', pg_name) + + def write_label(self, pg_name, content): + with open(os.path.join(self.data_dir(pg_name.encode('utf-8')), 'label'), 'w') as f: + f.write(content.encode('utf-8')) + + def read_label(self, pg_name): + content = None + try: + with open(os.path.join(self.data_dir(pg_name.encode('utf-8')), 'label'), 'r') as f: + content = f.read() + except IOError: + return None + return content.strip() + + def start(self, pg_name, max_wait_limit=15, tags=None): if not self._is_running(pg_name): if pg_name in self._processes: del self._processes[pg_name] cwd = self.patroni_path self._log[pg_name] = open(os.path.join(self._output_dir, 'patroni_{0}.log'.format(pg_name)), 'a') - self._config[pg_name] = self._make_patroni_test_config(pg_name) + self._config[pg_name] = self._make_patroni_test_config(pg_name, tags=tags) p = subprocess.Popen(['python', 'patroni.py', self._config[pg_name]], stdout=self._log[pg_name], stderr=subprocess.STDOUT, cwd=cwd) @@ -113,7 +129,7 @@ class PatroniController(object): def _is_running(self, pg_name): return pg_name in self._processes and self._processes[pg_name].pid and (self._processes[pg_name].poll() is None) - def _make_patroni_test_config(self, pg_name): + def _make_patroni_test_config(self, pg_name, tags=None): patroni_config_name = PatroniController.PATRONI_CONFIG.format(pg_name) patroni_config_path = os.path.join(self._output_dir, patroni_config_name) @@ -121,6 +137,7 @@ class PatroniController(object): config = yaml.load(f) postgresql = config['postgresql'] postgresql['name'] = pg_name.encode('utf-8') + postgresql['data_dir'] = 'data/{0}'.format(pg_name.encode('utf-8')) postgresql_params = postgresql['parameters'] postgresql_params['logging_collector'] = 'on' postgresql_params['log_destination'] = 'csvlog' @@ -129,6 +146,11 @@ class PatroniController(object): postgresql_params['log_statement'] = 'all' postgresql_params['log_min_messages'] = 'debug1' + if tags: + config['tags'] = {} + for tag_name in tags: + config['tags'][tag_name.encode('utf-8')] = tags[tag_name].encode('utf-8') + with open(patroni_config_path, 'w') as f: yaml.dump(config, f, default_flow_style=False) @@ -141,7 +163,7 @@ class PatroniController(object): patroni_path = self.patroni_path with open(os.path.join(patroni_path, PatroniController.PATRONI_CONFIG.format(pg_name)), 'r') as f: config = yaml.load(f) - except OSError: + except IOError: return None connstring = config['postgresql']['connect_address'] if ':' in connstring: