def test_pause_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Cluster is already paused' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['pause', 'dummy', '--wait']) assert "'pause' request sent" in result.output mock_get_dcs.return_value.get_cluster = Mock(side_effect=[ get_cluster_initialized_with_leader(), get_cluster(None, None, [], None, None) ]) self.runner.invoke(ctl, ['pause', 'dummy', '--wait']) member = Member(1, 'other', 28, {}) mock_get_dcs.return_value.get_cluster = Mock(side_effect=[ get_cluster_initialized_with_leader(), get_cluster(None, None, [member], None, None) ]) self.runner.invoke(ctl, ['pause', 'dummy', '--wait'])
def test_restart_reinit(self): runner = CliRunner() result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='y') result = runner.invoke(ctl, ['reinit', 'alpha', '--dcs', '8.8.8.8'], input='y') result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='N') result = runner.invoke(ctl, [ 'restart', 'alpha', '--dcs', '8.8.8.8', 'dummy', '--any', ], input='y') assert 'not a member' in str(result.exception) with patch('requests.post', Mock(return_value=MockResponse())): result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='y')
def test_pause_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Success' in result.output with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Cluster is already paused' in result.output
def test_resume_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Success' in result.output with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=False)): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Cluster is not paused' in result.output with patch('requests.patch', Mock(side_effect=Exception)): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Can not find accessible cluster member' in result.output
def test_flush(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '-r', 'master'], input='y') assert 'No scheduled restart' in result.output result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Success: flush scheduled restart' in result.output with patch.object(requests, 'delete', return_value=MockResponse(404)): result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Failed: flush scheduled restart' in result.output
def test_restart_reinit(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y') assert 'restart failed for' in result.output assert result.exit_code == 0 result = self.runner.invoke(ctl, ['reinit', 'alpha'], input='y') assert result.exit_code == 1 # Aborted restart result = self.runner.invoke(ctl, ['restart', 'alpha'], input='N') assert result.exit_code == 1 # Not a member result = self.runner.invoke(ctl, ['restart', 'alpha', 'dummy', '--any'], input='y') assert result.exit_code == 1 with patch('requests.post', Mock(return_value=MockResponse())): result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y') assert result.exit_code == 0
class TestCtl(unittest.TestCase): @patch('socket.getaddrinfo', socket_getaddrinfo) def setUp(self): with patch.object(Client, 'machines') as mock_machines: mock_machines.__get__ = Mock(return_value=['http://*****:*****@patch('psycopg2.connect', psycopg2_connect) def test_get_cursor(self): self.assertIsNone(get_cursor(get_cluster_initialized_without_leader(), {}, role='master')) self.assertIsNotNone(get_cursor(get_cluster_initialized_with_leader(), {}, role='master')) # MockCursor returns pg_is_in_recovery as false self.assertIsNone(get_cursor(get_cluster_initialized_with_leader(), {}, role='replica')) self.assertIsNotNone(get_cursor(get_cluster_initialized_with_leader(), {'database': 'foo'}, role='any')) def test_parse_dcs(self): assert parse_dcs(None) is None assert parse_dcs('localhost') == {'etcd': {'host': 'localhost:2379'}} assert parse_dcs('') == {'etcd': {'host': 'localhost:2379'}} assert parse_dcs('localhost:8500') == {'consul': {'host': 'localhost:8500'}} assert parse_dcs('zookeeper://localhost') == {'zookeeper': {'hosts': ['localhost:2181']}} assert parse_dcs('exhibitor://dummy') == {'exhibitor': {'hosts': ['dummy'], 'port': 8181}} assert parse_dcs('consul://localhost') == {'consul': {'host': 'localhost:8500'}} self.assertRaises(PatroniCtlException, parse_dcs, 'invalid://test') def test_output_members(self): scheduled_at = datetime.now(tzutc) + timedelta(seconds=600) cluster = get_cluster_initialized_with_leader(Failover(1, 'foo', 'bar', scheduled_at)) self.assertIsNone(output_members(cluster, name='abc', fmt='pretty')) self.assertIsNone(output_members(cluster, name='abc', fmt='json')) self.assertIsNone(output_members(cluster, name='abc', fmt='yaml')) self.assertIsNone(output_members(cluster, name='abc', fmt='tsv')) @patch('patroni.ctl.get_dcs') @patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse())) def test_switchover(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader mock_get_dcs.return_value.set_failover_value = Mock() result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\ny') assert 'leader' in result.output result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n2300-01-01T12:23:00\ny') assert result.exit_code == 0 with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['switchover', 'dummy', '--force', '--scheduled', '2015-01-01T12:00:00']) assert result.exit_code == 1 # Aborting switchover, as we anser NO to the confirmation result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\nN') assert result.exit_code == 1 # Target and source are equal result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nleader\n\ny') assert result.exit_code == 1 # Reality is not part of this cluster result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nReality\n\ny') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['switchover', 'dummy', '--force']) assert 'Member' in result.output result = self.runner.invoke(ctl, ['switchover', 'dummy', '--force', '--scheduled', '2015-01-01T12:00:00+01:00']) assert result.exit_code == 0 # Invalid timestamp result = self.runner.invoke(ctl, ['switchover', 'dummy', '--force', '--scheduled', 'invalid']) assert result.exit_code != 0 # Invalid timestamp result = self.runner.invoke(ctl, ['switchover', 'dummy', '--force', '--scheduled', '2115-02-30T12:00:00+01:00']) assert result.exit_code != 0 # Specifying wrong leader result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='dummy') assert result.exit_code == 1 with patch('patroni.ctl.request_patroni', Mock(side_effect=Exception)): # Non-responding patroni result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n2300-01-01T12:23:00\ny') assert 'falling back to DCS' in result.output with patch('patroni.ctl.request_patroni') as mocked: mocked.return_value.status_code = 500 result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\ny') assert 'Switchover failed' in result.output mocked.return_value.status_code = 501 mocked.return_value.text = 'Server does not support this operation' result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\ny') assert 'Switchover failed' in result.output # No members available mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_only_leader result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\ny') assert result.exit_code == 1 # No master available mock_get_dcs.return_value.get_cluster = get_cluster_initialized_without_leader result = self.runner.invoke(ctl, ['switchover', 'dummy'], input='leader\nother\n\ny') assert result.exit_code == 1 @patch('patroni.ctl.get_dcs') @patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse())) def test_failover(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader mock_get_dcs.return_value.set_failover_value = Mock() result = self.runner.invoke(ctl, ['failover', 'dummy'], input='\n') assert 'Failover could be performed only to a specific candidate' in result.output def test_get_dcs(self): self.assertRaises(PatroniCtlException, get_dcs, {'dummy': {}}, 'dummy') @patch('psycopg2.connect', psycopg2_connect) @patch('patroni.ctl.query_member', Mock(return_value=([['mock column']], None))) @patch('patroni.ctl.get_dcs') @patch.object(etcd.Client, 'read', etcd_read) def test_query(self, mock_get_dcs): mock_get_dcs.return_value = self.e # Mutually exclusive result = self.runner.invoke(ctl, ['query', 'alpha', '--member', 'abc', '--role', 'master']) assert result.exit_code == 1 with self.runner.isolated_filesystem(): with open('dummy', 'w') as dummy_file: dummy_file.write('SELECT 1') # Mutually exclusive result = self.runner.invoke(ctl, ['query', 'alpha', '--file', 'dummy', '--command', 'dummy']) assert result.exit_code == 1 result = self.runner.invoke(ctl, ['query', 'alpha', '--file', 'dummy']) assert result.exit_code == 0 os.remove('dummy') result = self.runner.invoke(ctl, ['query', 'alpha', '--command', 'SELECT 1']) assert 'mock column' in result.output # --command or --file is mandatory result = self.runner.invoke(ctl, ['query', 'alpha']) assert result.exit_code == 1 result = self.runner.invoke(ctl, ['query', 'alpha', '--command', 'SELECT 1', '--username', 'root', '--password', '--dbname', 'postgres'], input='ab\nab') assert 'mock column' in result.output def test_query_member(self): with patch('patroni.ctl.get_cursor', Mock(return_value=MockConnect().cursor())): rows = query_member(None, None, None, 'master', 'SELECT pg_catalog.pg_is_in_recovery()', {}) self.assertTrue('False' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_catalog.pg_is_in_recovery()', {}) self.assertEqual(rows, (None, None)) with patch('test_postgresql.MockCursor.execute', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_catalog.pg_is_in_recovery()', {}) with patch('patroni.ctl.get_cursor', Mock(return_value=None)): rows = query_member(None, None, None, None, 'SELECT pg_catalog.pg_is_in_recovery()', {}) self.assertTrue('No connection to' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_catalog.pg_is_in_recovery()', {}) self.assertTrue('No connection to' in str(rows)) with patch('patroni.ctl.get_cursor', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_catalog.pg_is_in_recovery()', {}) @patch('patroni.ctl.get_dcs') def test_dsn(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['dsn', 'alpha']) assert 'host=127.0.0.1 port=5435' in result.output # Mutually exclusive options result = self.runner.invoke(ctl, ['dsn', 'alpha', '--role', 'master', '--member', 'dummy']) assert result.exit_code == 1 # Non-existing member result = self.runner.invoke(ctl, ['dsn', 'alpha', '--member', 'dummy']) assert result.exit_code == 1 @patch('requests.post') @patch('patroni.ctl.get_dcs') def test_reload(self, mock_get_dcs, mock_post): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['reload', 'alpha'], input='y') assert 'Failed: reload for member' in result.output mock_post.return_value.status_code = 200 result = self.runner.invoke(ctl, ['reload', 'alpha'], input='y') assert 'No changes to apply on member' in result.output mock_post.return_value.status_code = 202 result = self.runner.invoke(ctl, ['reload', 'alpha'], input='y') assert 'Reload request received for member' in result.output @patch('requests.post', requests_get) @patch('patroni.ctl.get_dcs') def test_restart_reinit(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y\n\nnow') assert 'Failed: restart for' in result.output assert result.exit_code == 0 result = self.runner.invoke(ctl, ['reinit', 'alpha'], input='y') assert result.exit_code == 1 # successful reinit result = self.runner.invoke(ctl, ['reinit', 'alpha', 'other'], input='y\ny') assert result.exit_code == 0 # Aborted restart result = self.runner.invoke(ctl, ['restart', 'alpha'], input='N') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['restart', 'alpha', '--pending', '--force']) assert result.exit_code == 0 # Not a member result = self.runner.invoke(ctl, ['restart', 'alpha', 'dummy', '--any'], input='y') assert result.exit_code == 1 # Wrong pg version result = self.runner.invoke(ctl, ['restart', 'alpha', '--any', '--pg-version', '9.1'], input='y') assert 'Error: Invalid PostgreSQL version format' in result.output assert result.exit_code == 1 result = self.runner.invoke(ctl, ['restart', 'alpha', '--pending', '--force', '--timeout', '10min']) assert result.exit_code == 0 with patch('requests.delete', Mock(return_value=MockResponse(500))): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert 'Failed: flush scheduled restart' in result.output with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert result.exit_code == 1 with patch('requests.post', Mock(return_value=MockResponse())): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', '--pg-version', '42.0.0', '--scheduled', '2300-10-01T14:30'], input='y') assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(204))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', '--pg-version', '42.0', '--scheduled', '2300-10-01T14:30'], input='y') assert result.exit_code == 0 # force restart with restart already present with patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse(204))): result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(202))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke( ctl, ['restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30'], input='y' ) assert 'Success: restart scheduled' in result.output assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(409))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke( ctl, ['restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30'], input='y' ) assert 'Failed: another restart is already' in result.output assert result.exit_code == 0 @patch('patroni.ctl.get_dcs') def test_remove(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nslave') assert 'Please confirm' in result.output assert 'You are about to remove all' in result.output # Not typing an exact confirmation assert result.exit_code == 1 # master specified does not match master of cluster result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nYes I am aware\nslave') assert result.exit_code == 1 # cluster specified on cmdline does not match verification prompt result = self.runner.invoke(ctl, ['remove', 'alpha'], input='beta\nleader') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nYes I am aware\nleader') assert result.exit_code == 0 @patch('requests.post', Mock(side_effect=requests.exceptions.ConnectionError('foo'))) @patch('click.get_current_context') def test_request_patroni(self, mock_context): member = get_cluster_initialized_with_leader().leader.member mock_context.return_value.obj = {'ctl': {'cacert': 'cert.pem'}} self.assertRaises(requests.exceptions.ConnectionError, request_patroni, member, 'post', 'dummy', {}) mock_context.return_value.obj = {'ctl': {'insecure': True}} self.assertRaises(requests.exceptions.ConnectionError, request_patroni, member, 'post', 'dummy', {}) def test_ctl(self): self.runner.invoke(ctl, ['list']) result = self.runner.invoke(ctl, ['--help']) assert 'Usage:' in result.output def test_get_any_member(self): self.assertIsNone(get_any_member(get_cluster_initialized_without_leader(), role='master')) m = get_any_member(get_cluster_initialized_with_leader(), role='master') self.assertEqual(m.name, 'leader') def test_get_all_members(self): self.assertEqual(list(get_all_members(get_cluster_initialized_without_leader(), role='master')), []) r = list(get_all_members(get_cluster_initialized_with_leader(), role='master')) self.assertEqual(len(r), 1) self.assertEqual(r[0].name, 'leader') r = list(get_all_members(get_cluster_initialized_with_leader(), role='replica')) self.assertEqual(len(r), 1) self.assertEqual(r[0].name, 'other') self.assertEqual(len(list(get_all_members(get_cluster_initialized_without_leader(), role='replica'))), 2) @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(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']) assert result.exit_code == 0 @patch('patroni.ctl.get_dcs') def test_scaffold(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_not_initialized_without_leader mock_get_dcs.return_value.initialize = Mock(return_value=True) mock_get_dcs.return_value.touch_member = Mock(return_value=True) mock_get_dcs.return_value.attempt_to_acquire_leader = Mock(return_value=True) mock_get_dcs.return_value.delete_cluster = Mock() with patch.object(self.e, 'initialize', return_value=False): result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception with patch.object(mock_get_dcs.return_value, 'touch_member', Mock(return_value=False)): result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exit_code == 0 mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception @patch('patroni.ctl.get_dcs') def test_list_extended(self, mock_get_dcs): mock_get_dcs.return_value = self.e cluster = get_cluster_initialized_with_leader(sync=('leader', 'other')) mock_get_dcs.return_value.get_cluster = Mock(return_value=cluster) result = self.runner.invoke(ctl, ['list', 'dummy', '--extended', '--timestamp']) assert '2100' in result.output assert 'Scheduled restart' in result.output @patch('patroni.ctl.get_dcs') @patch('requests.delete', Mock(return_value=MockResponse())) def test_flush(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '-r', 'master'], input='y') assert 'No scheduled restart' in result.output result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Success: flush scheduled restart' in result.output with patch.object(requests, 'delete', return_value=MockResponse(404)): result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Failed: flush scheduled restart' in result.output @patch('patroni.ctl.get_dcs') @patch('patroni.ctl.polling_loop', Mock(return_value=[1])) def test_pause_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Cluster is already paused' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['pause', 'dummy', '--wait']) assert "'pause' request sent" in result.output mock_get_dcs.return_value.get_cluster = Mock(side_effect=[get_cluster_initialized_with_leader(), get_cluster(None, None, [], None, None)]) self.runner.invoke(ctl, ['pause', 'dummy', '--wait']) member = Member(1, 'other', 28, {}) mock_get_dcs.return_value.get_cluster = Mock(side_effect=[get_cluster_initialized_with_leader(), get_cluster(None, None, [member], None, None)]) self.runner.invoke(ctl, ['pause', 'dummy', '--wait']) @patch('patroni.ctl.get_dcs') def test_resume_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Success' in result.output with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=False)): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Cluster is not paused' in result.output with patch('requests.patch', Mock(side_effect=Exception)): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Can not find accessible cluster member' in result.output def test_apply_config_changes(self): config = {"postgresql": {"parameters": {"work_mem": "4MB"}, "use_pg_rewind": True}, "ttl": 30} before_editing = format_config_for_editing(config) # Spaces are allowed and stripped, numbers and booleans are interpreted after_editing, changed_config = apply_config_changes(before_editing, config, ["postgresql.parameters.work_mem = 5MB", "ttl=15", "postgresql.use_pg_rewind=off", 'a.b=c']) self.assertEqual(changed_config, {"a": {"b": "c"}, "postgresql": {"parameters": {"work_mem": "5MB"}, "use_pg_rewind": False}, "ttl": 15}) # postgresql.parameters namespace is flattened after_editing, changed_config = apply_config_changes(before_editing, config, ["postgresql.parameters.work_mem.sub = x"]) self.assertEqual(changed_config, {"postgresql": {"parameters": {"work_mem": "4MB", "work_mem.sub": "x"}, "use_pg_rewind": True}, "ttl": 30}) # Setting to null deletes after_editing, changed_config = apply_config_changes(before_editing, config, ["postgresql.parameters.work_mem=null"]) self.assertEqual(changed_config, {"postgresql": {"use_pg_rewind": True}, "ttl": 30}) after_editing, changed_config = apply_config_changes(before_editing, config, ["postgresql.use_pg_rewind=null", "postgresql.parameters.work_mem=null"]) self.assertEqual(changed_config, {"ttl": 30}) self.assertRaises(PatroniCtlException, apply_config_changes, before_editing, config, ['a']) @patch('sys.stdout.isatty', return_value=False) @patch('cdiff.markup_to_pager') def test_show_diff(self, mock_markup_to_pager, mock_isatty): show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n") mock_markup_to_pager.assert_not_called() mock_isatty.return_value = True show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n") mock_markup_to_pager.assert_called_once() # Test that unicode handling doesn't fail with an exception show_diff(b"foo:\n bar: \xc3\xb6\xc3\xb6\n".decode('utf-8'), b"foo:\n bar: \xc3\xbc\xc3\xbc\n".decode('utf-8')) @patch('subprocess.call', return_value=1) def test_invoke_editor(self, mock_subprocess_call): for e in ('', 'false'): os.environ['EDITOR'] = e self.assertRaises(PatroniCtlException, invoke_editor, 'foo: bar\n', 'test') @patch('patroni.ctl.get_dcs') def test_show_config(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader self.runner.invoke(ctl, ['show-config', 'dummy']) @patch('patroni.ctl.get_dcs') def test_edit_config(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader mock_get_dcs.return_value.set_config_value = Mock(return_value=False) os.environ['EDITOR'] = 'true' self.runner.invoke(ctl, ['edit-config', 'dummy']) self.runner.invoke(ctl, ['edit-config', 'dummy', '-s', 'foo=bar']) self.runner.invoke(ctl, ['edit-config', 'dummy', '--replace', 'postgres0.yml']) self.runner.invoke(ctl, ['edit-config', 'dummy', '--apply', '-'], input='foo: bar') self.runner.invoke(ctl, ['edit-config', 'dummy', '--force', '--apply', '-'], input='foo: bar') mock_get_dcs.return_value.set_config_value.return_value = True self.runner.invoke(ctl, ['edit-config', 'dummy', '--force', '--apply', '-'], input='foo: bar') @patch('patroni.ctl.get_dcs') def test_version(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('patroni.ctl.request_patroni') as mocked: result = self.runner.invoke(ctl, ['version']) assert 'patronictl version' in result.output mocked.return_value.json = lambda: {'patroni': {'version': '1.2.3'}, 'server_version': 100001} result = self.runner.invoke(ctl, ['version', 'dummy']) assert '1.2.3' in result.output with patch('requests.get', Mock(side_effect=Exception)): result = self.runner.invoke(ctl, ['version', 'dummy']) assert 'failed to get version' in result.output def test_format_pg_version(self): self.assertEqual(format_pg_version(100001), '10.1') self.assertEqual(format_pg_version(90605), '9.6.5')
def test_restart_reinit(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y\n\nnow') assert 'Failed: restart for' in result.output assert result.exit_code == 0 result = self.runner.invoke(ctl, ['reinit', 'alpha'], input='y') assert result.exit_code == 1 # successful reinit result = self.runner.invoke(ctl, ['reinit', 'alpha', 'other'], input='y\ny') assert result.exit_code == 0 # Aborted restart result = self.runner.invoke(ctl, ['restart', 'alpha'], input='N') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['restart', 'alpha', '--pending', '--force']) assert result.exit_code == 0 # Not a member result = self.runner.invoke(ctl, ['restart', 'alpha', 'dummy', '--any'], input='y') assert result.exit_code == 1 # Wrong pg version result = self.runner.invoke(ctl, ['restart', 'alpha', '--any', '--pg-version', '9.1'], input='y') assert 'Error: Invalid PostgreSQL version format' in result.output assert result.exit_code == 1 result = self.runner.invoke(ctl, ['restart', 'alpha', '--pending', '--force', '--timeout', '10min']) assert result.exit_code == 0 with patch('requests.delete', Mock(return_value=MockResponse(500))): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert 'Failed: flush scheduled restart' in result.output with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert result.exit_code == 1 with patch('requests.post', Mock(return_value=MockResponse())): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', '--pg-version', '42.0.0', '--scheduled', '2300-10-01T14:30'], input='y') assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(204))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, ['restart', 'alpha', '--pg-version', '42.0', '--scheduled', '2300-10-01T14:30'], input='y') assert result.exit_code == 0 # force restart with restart already present with patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse(204))): result = self.runner.invoke(ctl, ['restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30']) assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(202))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke( ctl, ['restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30'], input='y' ) assert 'Success: restart scheduled' in result.output assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(409))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke( ctl, ['restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30'], input='y' ) assert 'Failed: another restart is already' in result.output assert result.exit_code == 0
class TestCtl(unittest.TestCase): @patch('socket.getaddrinfo', socket_getaddrinfo) @patch.object(Client, 'machines') def setUp(self, mock_machines): mock_machines.__get__ = Mock(return_value=['http://*****:*****@patch('psycopg2.connect', psycopg2_connect) def test_get_cursor(self): c = get_cursor(get_cluster_initialized_without_leader(), role='master') assert c is None c = get_cursor(get_cluster_initialized_with_leader(), role='master') assert c is not None c = get_cursor(get_cluster_initialized_with_leader(), role='replica') # # MockCursor returns pg_is_in_recovery as false assert c is None c = get_cursor(get_cluster_initialized_with_leader(), role='any') assert c is not None def test_output_members(self): cluster = get_cluster_initialized_with_leader() output_members(cluster, name='abc', format='pretty') output_members(cluster, name='abc', format='json') output_members(cluster, name='abc', format='tsv') @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) @patch('patroni.etcd.Etcd.set_failover_value', Mock(return_value=None)) @patch('patroni.ctl.wait_for_leader', Mock(return_value=get_cluster_initialized_with_leader())) @patch('requests.get', requests_get) @patch('requests.post', requests_get) @patch('patroni.ctl.post_patroni', Mock(return_value=MockResponse())) def test_failover(self): runner = CliRunner() with patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())): result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other y''') assert 'Failing over to new leader' in result.output result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other N''') assert 'Aborting failover' in str(result.exception) result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader leader y''') assert 'target and source are the same' in str(result.exception) result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader Reality y''') assert 'Reality does not exist' in str(result.exception) result = runner.invoke(ctl, ['failover', 'dummy', '--force']) assert 'Failing over to new leader' in result.output result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='dummy') assert 'is not the leader of cluster' in str(result.exception) with patch( 'patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_only_leader())): result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other y''') assert 'No candidates found to failover to' in str( result.exception) with patch( 'patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_without_leader())): result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other y''') assert 'This cluster has no master' in str(result.exception) with patch('patroni.ctl.post_patroni', Mock(side_effect=Exception())): result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other y''') assert 'falling back to DCS' in result.output assert 'Failover failed' in result.output mocked = Mock() mocked.return_value.status_code = 500 with patch('patroni.ctl.post_patroni', Mock(return_value=mocked)): result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader other y''') assert 'Failover failed, details' in result.output # with patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())): # result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='nonsense') # assert 'is not the leader of cluster' in str(result.exception) # result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8', '--master', 'nonsense']) # assert 'is not the leader of cluster' in str(result.exception) # result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='leader\nother\nn') # assert 'Aborting failover' in str(result.exception) # with patch('patroni.ctl.wait_for_leader', Mock(return_value = get_cluster_initialized_with_leader())): # result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='leader\nother\nY') # assert 'master did not change after' in result.output # result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='leader\nother\nY') # assert 'Failover failed' in result.output def test_(self): self.assertRaises(patroni.exceptions.PatroniCtlException, get_dcs, {'scheme': 'dummy'}, 'dummy') @patch('psycopg2.connect', psycopg2_connect) @patch('patroni.ctl.query_member', Mock(return_value=([['mock column']], None))) def test_query(self): runner = CliRunner() with patch('patroni.ctl.get_dcs', Mock(return_value=self.e)): result = runner.invoke(ctl, [ 'query', 'alpha', '--member', 'abc', '--role', 'master', ]) assert 'mutually exclusive' in str(result.exception) with runner.isolated_filesystem(): dummy_file = open('dummy', 'w') dummy_file.write('SELECT 1') dummy_file.close() result = runner.invoke(ctl, [ 'query', 'alpha', '--file', 'dummy', '--command', 'dummy', ]) assert 'mutually exclusive' in str(result.exception) result = runner.invoke(ctl, ['query', 'alpha', '--file', 'dummy']) os.remove('dummy') result = runner.invoke(ctl, ['query', 'alpha', '--command', 'SELECT 1']) assert 'mock column' in result.output @patch('patroni.ctl.get_cursor', Mock(return_value=MockConnect().cursor())) def test_query_member(self): rows = query_member(None, None, None, 'master', 'SELECT pg_is_in_recovery()') assert 'False' in str(rows) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') assert rows == (None, None) with patch('patroni.ctl.get_cursor', Mock(return_value=None)): rows = query_member(None, None, None, None, 'SELECT pg_is_in_recovery()') assert 'No connection to' in str(rows) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') assert 'No connection to' in str(rows) with patch('patroni.ctl.get_cursor', Mock(side_effect=psycopg2.OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') with patch('test_postgresql.MockCursor.execute', Mock(side_effect=psycopg2.OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') @patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) def test_dsn(self): runner = CliRunner() with patch('patroni.ctl.get_dcs', Mock(return_value=self.e)): result = runner.invoke(ctl, ['dsn', 'alpha', '--dcs', '8.8.8.8']) assert 'host=127.0.0.1 port=5435' in result.output result = runner.invoke(ctl, [ 'dsn', 'alpha', '--role', 'master', '--member', 'dummy', ]) assert 'mutually exclusive' in str(result.exception) result = runner.invoke(ctl, ['dsn', 'alpha', '--member', 'dummy']) assert 'Can not find' in str(result.exception) # result = runner.invoke(ctl, ['dsn', 'alpha', '--dcs', '8.8.8.8', '--role', 'replica']) # assert 'host=127.0.0.1 port=5436' in result.output @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) @patch('requests.get', requests_get) @patch('requests.post', requests_get) def test_restart_reinit(self): runner = CliRunner() result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='y') result = runner.invoke(ctl, ['reinit', 'alpha', '--dcs', '8.8.8.8'], input='y') result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='N') result = runner.invoke(ctl, [ 'restart', 'alpha', '--dcs', '8.8.8.8', 'dummy', '--any', ], input='y') assert 'not a member' in str(result.exception) with patch('requests.post', Mock(return_value=MockResponse())): result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='y') @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) def test_remove(self): runner = CliRunner() result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='alpha\nslave') assert 'Please confirm' in result.output assert 'You are about to remove all' in result.output assert 'You did not exactly type' in str(result.exception) result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='''alpha Yes I am aware slave''') assert 'You did not specify the current master of the cluster' in str( result.exception) result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='beta\nleader') assert 'Cluster names specified do not match' in str(result.exception) with patch('patroni.etcd.Etcd.get_cluster', get_cluster_initialized_with_leader): result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='''alpha Yes I am aware leader''') assert 'object has no attribute' in str(result.exception) with patch('patroni.ctl.get_dcs', Mock(return_value=Mock())): result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='''alpha Yes I am aware leader''') assert 'We have not implemented this for DCS of type' in str( result.exception) @patch('patroni.etcd.Etcd.watch', Mock(return_value=None)) @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) def test_wait_for_leader(self): dcs = self.e self.assertRaises(patroni.exceptions.PatroniCtlException, wait_for_leader, dcs, 0) cluster = wait_for_leader(dcs=dcs, timeout=2) assert cluster.leader.member.name == 'leader' def test_post_patroni(self): member = get_cluster_initialized_with_leader().leader.member self.assertRaises(requests.exceptions.ConnectionError, post_patroni, member, 'dummy', {}) def test_ctl(self): runner = CliRunner() runner.invoke(ctl, ['list']) result = runner.invoke(ctl, ['--help']) assert 'Usage:' in result.output def test_get_any_member(self): m = get_any_member(get_cluster_initialized_without_leader(), role='master') assert m is None m = get_any_member(get_cluster_initialized_with_leader(), role='master') assert m.name == 'leader' def test_get_all_members(self): r = list( get_all_members(get_cluster_initialized_without_leader(), role='master')) assert len(r) == 0 r = list( get_all_members(get_cluster_initialized_with_leader(), role='master')) assert len(r) == 1 assert r[0].name == 'leader' r = list( get_all_members(get_cluster_initialized_with_leader(), role='replica')) assert len(r) == 1 assert r[0].name == 'other' r = list( get_all_members(get_cluster_initialized_without_leader(), role='replica')) assert len(r) == 2 @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) @patch('requests.get', requests_get) @patch('requests.post', requests_get) def test_members(self): runner = CliRunner() result = runner.invoke(members, ['alpha']) assert result.exit_code == 0 def test_configure(self): runner = CliRunner() result = runner.invoke(configure, [ '--dcs', 'abc', '-c', 'dummy', '-n', 'bla', ]) assert result.exit_code == 0
class TestCtl(unittest.TestCase): @patch('socket.getaddrinfo', socket_getaddrinfo) def setUp(self): self.runner = CliRunner() with patch.object(Client, 'machines') as mock_machines: mock_machines.__get__ = Mock( return_value=['http://*****:*****@patch('psycopg2.connect', psycopg2_connect) def test_get_cursor(self): self.assertIsNone( get_cursor(get_cluster_initialized_without_leader(), role='master')) self.assertIsNotNone( get_cursor(get_cluster_initialized_with_leader(), role='master')) # MockCursor returns pg_is_in_recovery as false self.assertIsNone( get_cursor(get_cluster_initialized_with_leader(), role='replica')) self.assertIsNotNone( get_cursor(get_cluster_initialized_with_leader(), role='any')) def test_parse_dcs(self): assert parse_dcs(None) is None assert parse_dcs('localhost') == {'etcd': {'host': 'localhost:4001'}} assert parse_dcs('') == {'etcd': {'host': 'localhost:4001'}} assert parse_dcs('localhost:8500') == { 'consul': { 'host': 'localhost:8500' } } assert parse_dcs('zookeeper://localhost') == { 'zookeeper': { 'hosts': ['localhost:2181'] } } assert parse_dcs('exhibitor://dummy') == { 'zookeeper': { 'exhibitor': { 'hosts': ['dummy'], 'port': 8181 } } } assert parse_dcs('consul://localhost') == { 'consul': { 'host': 'localhost:8500' } } self.assertRaises(PatroniCtlException, parse_dcs, 'invalid://test') def test_output_members(self): cluster = get_cluster_initialized_with_leader() self.assertIsNone(output_members(cluster, name='abc', fmt='pretty')) self.assertIsNone(output_members(cluster, name='abc', fmt='json')) self.assertIsNone(output_members(cluster, name='abc', fmt='tsv')) @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) @patch('patroni.ctl.post_patroni', Mock(return_value=MockResponse())) def test_failover(self): result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\ny''') assert 'leader' in result.output result = self.runner.invoke( ctl, ['failover', 'dummy'], input='''leader\nother\n2100-01-01T12:23:00\ny''') assert result.exit_code == 0 result = self.runner.invoke( ctl, ['failover', 'dummy'], input='''leader\nother\n2030-01-01T12:23:00\ny''') assert result.exit_code == 0 # Aborting failover,as we anser NO to the confirmation result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\nN''') assert result.exit_code == 1 # Target and source are equal result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nleader\n\ny''') assert result.exit_code == 1 # Reality is not part of this cluster result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nReality\n\ny''') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['failover', 'dummy', '--force']) assert 'Member' in result.output result = self.runner.invoke(ctl, [ 'failover', 'dummy', '--force', '--scheduled', '2015-01-01T12:00:00+01:00' ]) assert result.exit_code == 0 # Invalid timestamp result = self.runner.invoke( ctl, ['failover', 'dummy', '--force', '--scheduled', 'invalid']) assert result.exit_code != 0 # Invalid timestamp result = self.runner.invoke(ctl, [ 'failover', 'dummy', '--force', '--scheduled', '2115-02-30T12:00:00+01:00' ]) assert result.exit_code != 0 # Specifying wrong leader result = self.runner.invoke(ctl, ['failover', 'dummy'], input='dummy') assert result.exit_code == 1 with patch( 'patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_only_leader())): # No members available result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\ny''') assert result.exit_code == 1 with patch( 'patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_without_leader())): # No master available result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\ny''') assert result.exit_code == 1 with patch('patroni.ctl.post_patroni', Mock(side_effect=Exception)): # Non-responding patroni result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\ny''') assert 'falling back to DCS' in result.output with patch('patroni.ctl.post_patroni') as mocked: mocked.return_value.status_code = 500 result = self.runner.invoke(ctl, ['failover', 'dummy'], input='''leader\nother\n\ny''') assert 'Failover failed' in result.output def test_get_dcs(self): self.assertRaises(PatroniCtlException, get_dcs, {'dummy': {}}, 'dummy') with patch('patroni.Patroni.get_dcs', Mock(return_value=self.e)): assert get_dcs({ 'etcd': { 'host': 'none' } }, 'dummy').client_path('') == '/service/test/' @patch('psycopg2.connect', psycopg2_connect) @patch('patroni.ctl.query_member', Mock(return_value=([['mock column']], None))) @patch.object(etcd.Client, 'read', etcd_read) def test_query(self): with patch('patroni.ctl.get_dcs', Mock(return_value=self.e)): # Mutually exclusive result = self.runner.invoke( ctl, ['query', 'alpha', '--member', 'abc', '--role', 'master']) assert result.exit_code == 1 with self.runner.isolated_filesystem(): with open('dummy', 'w') as dummy_file: dummy_file.write('SELECT 1') # Mutually exclusive result = self.runner.invoke(ctl, [ 'query', 'alpha', '--file', 'dummy', '--command', 'dummy' ]) assert result.exit_code == 1 result = self.runner.invoke( ctl, ['query', 'alpha', '--file', 'dummy']) assert result.exit_code == 0 os.remove('dummy') result = self.runner.invoke( ctl, ['query', 'alpha', '--command', 'SELECT 1']) assert 'mock column' in result.output # --command or --file is mandatory result = self.runner.invoke(ctl, ['query', 'alpha']) assert result.exit_code == 1 result = self.runner.invoke(ctl, [ 'query', 'alpha', '--command', 'SELECT 1', '--username', 'root', '--password', '--dbname', 'postgres' ], input='ab\nab') assert 'mock column' in result.output def test_query_member(self): with patch('patroni.ctl.get_cursor', Mock(return_value=MockConnect().cursor())): rows = query_member(None, None, None, 'master', 'SELECT pg_is_in_recovery()') self.assertTrue('False' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') self.assertEquals(rows, (None, None)) with patch('test_postgresql.MockCursor.execute', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') with patch('patroni.ctl.get_cursor', Mock(return_value=None)): rows = query_member(None, None, None, None, 'SELECT pg_is_in_recovery()') self.assertTrue('No connection to' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') self.assertTrue('No connection to' in str(rows)) with patch('patroni.ctl.get_cursor', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()') @patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) def test_dsn(self): with patch('patroni.ctl.get_dcs', Mock(return_value=self.e)): result = self.runner.invoke(ctl, ['dsn', 'alpha']) assert 'host=127.0.0.1 port=5435' in result.output # Mutually exclusive options result = self.runner.invoke( ctl, ['dsn', 'alpha', '--role', 'master', '--member', 'dummy']) assert result.exit_code == 1 # Non-existing member result = self.runner.invoke(ctl, ['dsn', 'alpha', '--member', 'dummy']) assert result.exit_code == 1 @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) @patch('requests.post', requests_get) def test_restart_reinit(self): result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y') assert 'restart failed for' in result.output assert result.exit_code == 0 result = self.runner.invoke(ctl, ['reinit', 'alpha'], input='y') assert result.exit_code == 1 # Aborted restart result = self.runner.invoke(ctl, ['restart', 'alpha'], input='N') assert result.exit_code == 1 # Not a member result = self.runner.invoke(ctl, ['restart', 'alpha', 'dummy', '--any'], input='y') assert result.exit_code == 1 with patch('requests.post', Mock(return_value=MockResponse())): result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y') assert result.exit_code == 0 @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch.object(etcd.Client, 'delete', Mock(side_effect=etcd.EtcdException)) def test_remove(self): with patch('patroni.ctl.get_dcs', Mock(return_value=self.e)): result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nslave') assert 'Please confirm' in result.output assert 'You are about to remove all' in result.output # Not typing an exact confirmation assert result.exit_code == 1 # master specified does not match master of cluster result = self.runner.invoke( ctl, ['remove', 'alpha'], input='''alpha\nYes I am aware\nslave''') assert result.exit_code == 1 # cluster specified on cmdline does not match verification prompt result = self.runner.invoke(ctl, ['remove', 'alpha'], input='beta\nleader') assert result.exit_code == 1 result = self.runner.invoke( ctl, ['remove', 'alpha'], input='''alpha\nYes I am aware\nleader''') assert result.exit_code == 0 @patch('patroni.etcd.Etcd.watch', Mock(return_value=None)) @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) def test_wait_for_leader(self): self.assertRaises(PatroniCtlException, wait_for_leader, self.e, 0) cluster = wait_for_leader(self.e, timeout=2) assert cluster.leader.member.name == 'leader' @patch('requests.post', Mock(side_effect=requests.exceptions.ConnectionError('foo'))) def test_post_patroni(self): member = get_cluster_initialized_with_leader().leader.member self.assertRaises(requests.exceptions.ConnectionError, post_patroni, member, 'dummy', {}) def test_ctl(self): self.runner.invoke(ctl, ['list']) result = self.runner.invoke(ctl, ['--help']) assert 'Usage:' in result.output def test_get_any_member(self): self.assertIsNone( get_any_member(get_cluster_initialized_without_leader(), role='master')) m = get_any_member(get_cluster_initialized_with_leader(), role='master') self.assertEquals(m.name, 'leader') def test_get_all_members(self): self.assertEquals( list( get_all_members(get_cluster_initialized_without_leader(), role='master')), []) r = list( get_all_members(get_cluster_initialized_with_leader(), role='master')) self.assertEquals(len(r), 1) self.assertEquals(r[0].name, 'leader') r = list( get_all_members(get_cluster_initialized_with_leader(), role='replica')) self.assertEquals(len(r), 1) self.assertEquals(r[0].name, 'other') self.assertEquals( len( list( get_all_members(get_cluster_initialized_without_leader(), role='replica'))), 2) @patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) @patch('patroni.etcd.Etcd.get_etcd_client', Mock(return_value=None)) def test_members(self): result = self.runner.invoke(members, ['alpha']) assert '127.0.0.1' in result.output assert result.exit_code == 0 def test_configure(self): result = self.runner.invoke( configure, ['--dcs', 'abc', '-c', 'dummy', '-n', 'bla']) assert result.exit_code == 0
class TestCtl(unittest.TestCase): @patch('socket.getaddrinfo', socket_getaddrinfo) def setUp(self): with patch.object(Client, 'machines') as mock_machines: mock_machines.__get__ = Mock( return_value=['http://*****:*****@patch('psycopg2.connect', psycopg2_connect) def test_get_cursor(self): self.assertIsNone( get_cursor(get_cluster_initialized_without_leader(), {}, role='master')) self.assertIsNotNone( get_cursor(get_cluster_initialized_with_leader(), {}, role='master')) # MockCursor returns pg_is_in_recovery as false self.assertIsNone( get_cursor(get_cluster_initialized_with_leader(), {}, role='replica')) self.assertIsNotNone( get_cursor(get_cluster_initialized_with_leader(), {'database': 'foo'}, role='any')) def test_parse_dcs(self): assert parse_dcs(None) is None assert parse_dcs('localhost') == {'etcd': {'host': 'localhost:2379'}} assert parse_dcs('') == {'etcd': {'host': 'localhost:2379'}} assert parse_dcs('localhost:8500') == { 'consul': { 'host': 'localhost:8500' } } assert parse_dcs('zookeeper://localhost') == { 'zookeeper': { 'hosts': ['localhost:2181'] } } assert parse_dcs('exhibitor://dummy') == { 'exhibitor': { 'hosts': ['dummy'], 'port': 8181 } } assert parse_dcs('consul://localhost') == { 'consul': { 'host': 'localhost:8500' } } self.assertRaises(PatroniCtlException, parse_dcs, 'invalid://test') def test_output_members(self): cluster = get_cluster_initialized_with_leader() self.assertIsNone(output_members(cluster, name='abc', fmt='pretty')) self.assertIsNone(output_members(cluster, name='abc', fmt='json')) self.assertIsNone(output_members(cluster, name='abc', fmt='tsv')) @patch('patroni.ctl.get_dcs') @patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse())) def test_failover(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nother\n\ny') assert 'leader' in result.output result = self.runner.invoke( ctl, ['failover', 'dummy'], input='leader\nother\n2300-01-01T12:23:00\ny') assert result.exit_code == 0 with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, [ 'failover', 'dummy', '--force', '--scheduled', '2015-01-01T12:00:00' ]) assert result.exit_code == 1 # Aborting failover,as we anser NO to the confirmation result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nother\n\nN') assert result.exit_code == 1 # Target and source are equal result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nleader\n\ny') assert result.exit_code == 1 # Reality is not part of this cluster result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nReality\n\ny') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['failover', 'dummy', '--force']) assert 'Member' in result.output result = self.runner.invoke(ctl, [ 'failover', 'dummy', '--force', '--scheduled', '2015-01-01T12:00:00+01:00' ]) assert result.exit_code == 0 # Invalid timestamp result = self.runner.invoke( ctl, ['failover', 'dummy', '--force', '--scheduled', 'invalid']) assert result.exit_code != 0 # Invalid timestamp result = self.runner.invoke(ctl, [ 'failover', 'dummy', '--force', '--scheduled', '2115-02-30T12:00:00+01:00' ]) assert result.exit_code != 0 # Specifying wrong leader result = self.runner.invoke(ctl, ['failover', 'dummy'], input='dummy') assert result.exit_code == 1 with patch('patroni.ctl.request_patroni', Mock(side_effect=Exception)): # Non-responding patroni result = self.runner.invoke( ctl, ['failover', 'dummy'], input='leader\nother\n2300-01-01T12:23:00\ny') assert 'falling back to DCS' in result.output with patch('patroni.ctl.request_patroni') as mocked: mocked.return_value.status_code = 500 result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nother\n\ny') assert 'Failover failed' in result.output # No members available mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_only_leader result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nother\n\ny') assert result.exit_code == 1 # No master available mock_get_dcs.return_value.get_cluster = get_cluster_initialized_without_leader result = self.runner.invoke(ctl, ['failover', 'dummy'], input='leader\nother\n\ny') assert result.exit_code == 1 def test_get_dcs(self): self.assertRaises(PatroniCtlException, get_dcs, {'dummy': {}}, 'dummy') @patch('psycopg2.connect', psycopg2_connect) @patch('patroni.ctl.query_member', Mock(return_value=([['mock column']], None))) @patch('patroni.ctl.get_dcs') @patch.object(etcd.Client, 'read', etcd_read) def test_query(self, mock_get_dcs): mock_get_dcs.return_value = self.e # Mutually exclusive result = self.runner.invoke( ctl, ['query', 'alpha', '--member', 'abc', '--role', 'master']) assert result.exit_code == 1 with self.runner.isolated_filesystem(): with open('dummy', 'w') as dummy_file: dummy_file.write('SELECT 1') # Mutually exclusive result = self.runner.invoke( ctl, ['query', 'alpha', '--file', 'dummy', '--command', 'dummy']) assert result.exit_code == 1 result = self.runner.invoke(ctl, ['query', 'alpha', '--file', 'dummy']) assert result.exit_code == 0 os.remove('dummy') result = self.runner.invoke( ctl, ['query', 'alpha', '--command', 'SELECT 1']) assert 'mock column' in result.output # --command or --file is mandatory result = self.runner.invoke(ctl, ['query', 'alpha']) assert result.exit_code == 1 result = self.runner.invoke(ctl, [ 'query', 'alpha', '--command', 'SELECT 1', '--username', 'root', '--password', '--dbname', 'postgres' ], input='ab\nab') assert 'mock column' in result.output def test_query_member(self): with patch('patroni.ctl.get_cursor', Mock(return_value=MockConnect().cursor())): rows = query_member(None, None, None, 'master', 'SELECT pg_is_in_recovery()', {}) self.assertTrue('False' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) self.assertEquals(rows, (None, None)) with patch('test_postgresql.MockCursor.execute', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) with patch('patroni.ctl.get_cursor', Mock(return_value=None)): rows = query_member(None, None, None, None, 'SELECT pg_is_in_recovery()', {}) self.assertTrue('No connection to' in str(rows)) rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) self.assertTrue('No connection to' in str(rows)) with patch('patroni.ctl.get_cursor', Mock(side_effect=OperationalError('bla'))): rows = query_member(None, None, None, 'replica', 'SELECT pg_is_in_recovery()', {}) @patch('patroni.ctl.get_dcs') def test_dsn(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['dsn', 'alpha']) assert 'host=127.0.0.1 port=5435' in result.output # Mutually exclusive options result = self.runner.invoke( ctl, ['dsn', 'alpha', '--role', 'master', '--member', 'dummy']) assert result.exit_code == 1 # Non-existing member result = self.runner.invoke(ctl, ['dsn', 'alpha', '--member', 'dummy']) assert result.exit_code == 1 @patch('requests.post', requests_get) @patch('patroni.ctl.get_dcs') def test_restart_reinit(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['restart', 'alpha'], input='y\n\nnow') assert 'Failed: restart for' in result.output assert result.exit_code == 0 result = self.runner.invoke(ctl, ['reinit', 'alpha'], input='y') assert result.exit_code == 1 # successful reinit result = self.runner.invoke(ctl, ['reinit', 'alpha', 'other'], input='y') assert result.exit_code == 0 # Aborted restart result = self.runner.invoke(ctl, ['restart', 'alpha'], input='N') assert result.exit_code == 1 result = self.runner.invoke( ctl, ['restart', 'alpha', '--pending', '--force']) assert result.exit_code == 0 # Not a member result = self.runner.invoke(ctl, ['restart', 'alpha', 'dummy', '--any'], input='y') assert result.exit_code == 1 # Wrong pg version result = self.runner.invoke( ctl, ['restart', 'alpha', '--any', '--pg-version', '9.1'], input='y') assert 'Error: PostgreSQL version' in result.output assert result.exit_code == 1 with patch('requests.delete', Mock(return_value=MockResponse(500))): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, [ 'restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30' ]) assert 'Failed: flush scheduled restart' in result.output with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, [ 'restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30' ]) assert result.exit_code == 1 with patch('requests.post', Mock(return_value=MockResponse())): # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, [ 'restart', 'alpha', '--pg-version', '42.0.0', '--scheduled', '2300-10-01T14:30' ], input='y') assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(204))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, [ 'restart', 'alpha', '--pg-version', '42.0.0', '--scheduled', '2300-10-01T14:30' ], input='y') assert result.exit_code == 0 # force restart with restart already present with patch('patroni.ctl.request_patroni', Mock(return_value=MockResponse(204))): result = self.runner.invoke(ctl, [ 'restart', 'alpha', 'other', '--force', '--scheduled', '2300-10-01T14:30' ]) assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(202))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, [ 'restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30' ], input='y') assert 'Success: restart scheduled' in result.output assert result.exit_code == 0 with patch('requests.post', Mock(return_value=MockResponse(409))): # get restart with the non-200 return code # normal restart, the schedule is actually parsed, but not validated in patronictl result = self.runner.invoke(ctl, [ 'restart', 'alpha', '--pg-version', '99.0.0', '--scheduled', '2300-10-01T14:30' ], input='y') assert 'Failed: another restart is already' in result.output assert result.exit_code == 0 @patch('patroni.ctl.get_dcs') def test_remove(self, mock_get_dcs): mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nslave') assert 'Please confirm' in result.output assert 'You are about to remove all' in result.output # Not typing an exact confirmation assert result.exit_code == 1 # master specified does not match master of cluster result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nYes I am aware\nslave') assert result.exit_code == 1 # cluster specified on cmdline does not match verification prompt result = self.runner.invoke(ctl, ['remove', 'alpha'], input='beta\nleader') assert result.exit_code == 1 result = self.runner.invoke(ctl, ['remove', 'alpha'], input='alpha\nYes I am aware\nleader') assert result.exit_code == 0 @patch('patroni.dcs.AbstractDCS.watch', Mock(return_value=None)) @patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())) def test_wait_for_leader(self): self.assertRaises(PatroniCtlException, wait_for_leader, self.e, 0) cluster = wait_for_leader(self.e, timeout=2) assert cluster.leader.member.name == 'leader' @patch('requests.post', Mock(side_effect=requests.exceptions.ConnectionError('foo'))) def test_request_patroni(self): member = get_cluster_initialized_with_leader().leader.member self.assertRaises(requests.exceptions.ConnectionError, request_patroni, member, 'post', 'dummy', {}) def test_ctl(self): self.runner.invoke(ctl, ['list']) result = self.runner.invoke(ctl, ['--help']) assert 'Usage:' in result.output def test_get_any_member(self): self.assertIsNone( get_any_member(get_cluster_initialized_without_leader(), role='master')) m = get_any_member(get_cluster_initialized_with_leader(), role='master') self.assertEquals(m.name, 'leader') def test_get_all_members(self): self.assertEquals( list( get_all_members(get_cluster_initialized_without_leader(), role='master')), []) r = list( get_all_members(get_cluster_initialized_with_leader(), role='master')) self.assertEquals(len(r), 1) self.assertEquals(r[0].name, 'leader') r = list( get_all_members(get_cluster_initialized_with_leader(), role='replica')) self.assertEquals(len(r), 1) self.assertEquals(r[0].name, 'other') self.assertEquals( len( list( get_all_members(get_cluster_initialized_without_leader(), role='replica'))), 2) @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']) assert '127.0.0.1' in result.output assert result.exit_code == 0 def test_configure(self): result = self.runner.invoke( configure, ['--dcs', 'abc', '-c', 'dummy', '-n', 'bla']) assert result.exit_code == 0 @patch('patroni.ctl.get_dcs') def test_scaffold(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_not_initialized_without_leader mock_get_dcs.return_value.initialize = Mock(return_value=True) mock_get_dcs.return_value.touch_member = Mock(return_value=True) mock_get_dcs.return_value.attempt_to_acquire_leader = Mock( return_value=True) with patch.object(self.e, 'initialize', return_value=False): result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception with patch.object(mock_get_dcs.return_value, 'touch_member', Mock(return_value=False)): result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exit_code == 0 mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke(ctl, ['scaffold', 'alpha']) assert result.exception @patch('patroni.ctl.get_dcs') def test_list_extended(self, mock_get_dcs): mock_get_dcs.return_value = self.e cluster = get_cluster_initialized_with_leader(sync=('leader', 'other')) mock_get_dcs.return_value.get_cluster = Mock(return_value=cluster) result = self.runner.invoke(ctl, ['list', 'dummy', '--extended']) assert '2100' in result.output assert 'Scheduled restart' in result.output @patch('patroni.ctl.get_dcs') @patch('requests.delete', Mock(return_value=MockResponse())) def test_flush(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader result = self.runner.invoke( ctl, ['flush', 'dummy', 'restart', '-r', 'master'], input='y') assert 'No scheduled restart' in result.output result = self.runner.invoke(ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Success: flush scheduled restart' in result.output with patch.object(requests, 'delete', return_value=MockResponse(404)): result = self.runner.invoke( ctl, ['flush', 'dummy', 'restart', '--force']) assert 'Failed: flush scheduled restart' in result.output @patch('patroni.ctl.get_dcs') def test_pause_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Success' in result.output with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): result = self.runner.invoke(ctl, ['pause', 'dummy']) assert 'Cluster is already paused' in result.output @patch('patroni.ctl.get_dcs') def test_resume_cluster(self, mock_get_dcs): mock_get_dcs.return_value = self.e mock_get_dcs.return_value.get_cluster = get_cluster_initialized_with_leader with patch('patroni.dcs.Cluster.is_paused', Mock(return_value=True)): with patch('requests.patch', Mock(return_value=MockResponse(200))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Success' in result.output with patch('requests.patch', Mock(return_value=MockResponse(500))): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Failed' in result.output with patch('requests.patch', Mock(return_value=MockResponse(200))),\ patch('patroni.dcs.Cluster.is_paused', Mock(return_value=False)): result = self.runner.invoke(ctl, ['resume', 'dummy']) assert 'Cluster is not paused' in result.output