From 6b01d2787fc85cc878cd62dd0d9c22673aaee409 Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin Date: Thu, 4 Jan 2018 12:26:13 +0100 Subject: [PATCH] More improvements in patronictl (#590) Make specifying cluster_name optional for some more commands. If it is not specified, it's value would be taken from config file. --- patroni/ctl.py | 35 ++++++++++++++++++----------------- tests/test_ctl.py | 8 +++++--- tests/test_ha.py | 1 + 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/patroni/ctl.py b/patroni/ctl.py index cdf58505..4c1f0758 100644 --- a/patroni/ctl.py +++ b/patroni/ctl.py @@ -101,6 +101,8 @@ option_format = click.option('--format', '-f', 'fmt', help='Output format (prett option_watchrefresh = click.option('-w', '--watch', type=float, help='Auto update the screen every X seconds') option_watch = click.option('-W', is_flag=True, help='Auto update the screen every 2 seconds') option_force = click.option('--force', is_flag=True, help='Do not ask for confirmation at any point') +arg_cluster_name = click.argument('cluster_name', required=False, + default=lambda: click.get_current_context().obj.get('scope')) @click.group() @@ -150,12 +152,12 @@ def print_output(columns, rows=None, alignment=None, fmt='pretty', header=True, click.echo(t) return - if fmt in ['json', 'yaml']: + if fmt in ['json', 'yaml', 'yml']: elements = [dict(zip(columns, r)) for r in rows] if fmt == 'json': click.echo(json.dumps(elements)) - elif fmt == 'yaml': - click.echo(yaml.safe_dump(elements, encoding=None, allow_unicode=True, width=200)) + elif fmt in ('yaml', 'yml'): + click.echo(yaml.safe_dump(elements, encoding=None, default_flow_style=False, allow_unicode=True, width=200)) if fmt == 'tsv': if columns is not None and header: @@ -277,7 +279,7 @@ def get_members(cluster, cluster_name, member_names, role, force, action): @click.option('--role', '-r', help='Give a dsn of any member with this role', type=click.Choice(['master', 'replica', 'any']), default=None) @click.option('--member', '-m', help='Generate a dsn for this member', type=str) -@click.argument('cluster_name') +@arg_cluster_name @click.pass_obj def dsn(obj, cluster_name, role, member): if role is not None and member is not None: @@ -295,7 +297,7 @@ def dsn(obj, cluster_name, role, member): @ctl.command('query', help='Query a Patroni PostgreSQL member') -@click.argument('cluster_name') +@arg_cluster_name @option_format @click.option('--format', 'fmt', help='Output format (pretty, json)', default='tsv') @click.option('--file', '-f', 'p_file', help='Execute the SQL commands from this file', type=click.File('rb')) @@ -534,7 +536,7 @@ def reinit(obj, cluster_name, member_names, force): @ctl.command('failover', help='Failover to a replica') -@click.argument('cluster_name') +@arg_cluster_name @click.option('--master', help='The name of the current master', default=None) @click.option('--candidate', help='The name of the candidate', default=None) @click.option('--scheduled', help='Timestamp of a scheduled failover in unambiguous format (e.g. ISO 8601)', @@ -702,25 +704,24 @@ def output_members(cluster, name, extended=False, fmt='pretty'): @ctl.command('list', help='List the Patroni members for a given Patroni') @click.argument('cluster_names', nargs=-1) @click.option('--extended', '-e', help='Show some extra information', is_flag=True) -@click.option('--timestamp', '-t', help='Print timestamp', is_flag=True) +@click.option('--timestamp', '-t', 'ts', help='Print timestamp', is_flag=True) @option_format @option_watch @option_watchrefresh @click.pass_obj -def members(obj, cluster_names, fmt, watch, w, extended, timestamp): +def members(obj, cluster_names, fmt, watch, w, extended, ts): if not cluster_names: - if 'scope' not in obj: - logging.warning('Listing members: No cluster names were provided') - return - else: + if 'scope' in obj: cluster_names = [obj['scope']] + if not cluster_names: + return logging.warning('Listing members: No cluster names were provided') for cluster_name in cluster_names: dcs = get_dcs(obj, cluster_name) for _ in watching(w, watch): - if timestamp: - click.echo(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + if ts: + click.echo(timestamp(0)) cluster = dcs.get_cluster() output_members(cluster, cluster_name, extended, fmt) @@ -842,14 +843,14 @@ def toggle_pause(config, cluster_name, paused): @ctl.command('pause', help='Disable auto failover') -@click.argument('cluster_name') +@arg_cluster_name @click.pass_obj def pause(obj, cluster_name): return toggle_pause(obj, cluster_name, True) @ctl.command('resume', help='Resume auto failover') -@click.argument('cluster_name') +@arg_cluster_name @click.pass_obj def resume(obj, cluster_name): return toggle_pause(obj, cluster_name, False) @@ -994,7 +995,7 @@ def invoke_editor(before_editing, cluster_name): @ctl.command('edit-config', help="Edit cluster configuration") -@click.argument('cluster_name') +@arg_cluster_name @click.option('--quiet', '-q', is_flag=True, help='Do not show changes') @click.option('--set', '-s', 'kvpairs', multiple=True, help='Set specific configuration value. Can be specified multiple times') diff --git a/tests/test_ctl.py b/tests/test_ctl.py index a7eb7c68..deafdc96 100644 --- a/tests/test_ctl.py +++ b/tests/test_ctl.py @@ -7,7 +7,7 @@ import unittest from click.testing import CliRunner from datetime import datetime, timedelta from mock import patch, Mock -from patroni.ctl import ctl, members, store_config, load_config, output_members, request_patroni, get_dcs, parse_dcs, \ +from patroni.ctl import ctl, store_config, load_config, output_members, request_patroni, get_dcs, parse_dcs, \ get_all_members, get_any_member, get_cursor, query_member, configure, PatroniCtlException, apply_config_changes, \ format_config_for_editing, show_diff, invoke_editor, format_pg_version from patroni.dcs.etcd import Client, Failover @@ -33,7 +33,7 @@ def test_rw_config(): @patch('patroni.ctl.load_config', - Mock(return_value={'postgresql': {'data_dir': '.', 'parameters': {}, 'retry_timeout': 5}, + Mock(return_value={'scope': 'alpha', 'postgresql': {'data_dir': '.', 'parameters': {}, 'retry_timeout': 5}, 'restapi': {'auth': 'u:p', 'listen': ''}, 'etcd': {'host': 'localhost:2379'}})) class TestCtl(unittest.TestCase): @@ -346,9 +346,11 @@ class TestCtl(unittest.TestCase): @patch('patroni.ctl.get_dcs') def test_members(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader - result = self.runner.invoke(members, ['alpha']) + result = self.runner.invoke(ctl, ['list']) assert '127.0.0.1' in result.output assert result.exit_code == 0 + with patch('patroni.ctl.load_config', Mock(return_value={})): + self.runner.invoke(ctl, ['list']) def test_configure(self): result = self.runner.invoke(configure, ['--dcs', 'abc', '-c', 'dummy', '-n', 'bla']) diff --git a/tests/test_ha.py b/tests/test_ha.py index 175c2672..ad513008 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -861,6 +861,7 @@ class TestHa(unittest.TestCase): def test_shutdown(self): self.p.is_running = false + self.ha.has_lock = true self.ha.shutdown() @patch('time.sleep', Mock())