Implement /history and /cluster endpoints (#1191)

The /history endpoint shows the content of the `history` key in DCS
The /cluster endpoint show all cluster members and some service info like pending and scheduled restarts or switchovers.

In addition to that implement `patronictl history`

Close #586
Close #675
Close #1133
This commit is contained in:
Alexander Kukushkin
2019-10-22 17:19:02 +02:00
committed by GitHub
parent f4623c4e8e
commit 367d787ff9
11 changed files with 164 additions and 101 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ class MockCursor(object):
self.results = [(1, 2)]
elif sql.startswith('SELECT pg_catalog.pg_is_in_recovery()'):
self.results = [(False, 2)]
elif sql.startswith('WITH replication_info AS ('):
elif sql.startswith('SELECT pg_catalog.to_char'):
replication_info = '[{"application_name":"walreceiver","client_addr":"1.2.3.4",' +\
'"state":"streaming","sync_state":"async","sync_priority":0}]'
self.results = [('', 0, '', '', '', '', False, replication_info)]
+13
View File
@@ -12,6 +12,7 @@ from patroni.utils import tzutc
from six import BytesIO as IO
from six.moves import BaseHTTPServer
from . import psycopg2_connect, MockCursor
from .test_ha import get_cluster_initialized_without_leader
future_restart_time = datetime.datetime.now(tzutc) + datetime.timedelta(days=5)
@@ -159,6 +160,7 @@ class TestRestApiHandler(unittest.TestCase):
def test_do_GET(self):
MockRestApiServer(RestApiHandler, 'GET /replica')
MockRestApiServer(RestApiHandler, 'GET /read-only')
with patch.object(RestApiHandler, 'get_postgresql_status', Mock(return_value={})):
MockRestApiServer(RestApiHandler, 'GET /replica')
with patch.object(RestApiHandler, 'get_postgresql_status', Mock(return_value={'role': 'master'})):
@@ -197,6 +199,17 @@ class TestRestApiHandler(unittest.TestCase):
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'POST /restart HTTP/1.0'))
MockRestApiServer(RestApiHandler, 'POST /restart HTTP/1.0\nAuthorization:')
@patch.object(MockPatroni, 'dcs')
def test_do_GET_cluster(self, mock_dcs):
mock_dcs.cluster = get_cluster_initialized_without_leader()
mock_dcs.cluster.members[1].data['xlog_location'] = 11
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /cluster'))
@patch.object(MockPatroni, 'dcs')
def test_do_GET_history(self, mock_dcs):
mock_dcs.cluster = get_cluster_initialized_without_leader()
self.assertIsNotNone(MockRestApiServer(RestApiHandler, 'GET /history'))
@patch.object(MockPatroni, 'dcs')
def test_do_GET_config(self, mock_dcs):
mock_dcs.cluster.config.data = {}
+7
View File
@@ -566,6 +566,13 @@ class TestCtl(unittest.TestCase):
result = self.runner.invoke(ctl, ['version', 'dummy'])
assert 'failed to get version' in result.output
@patch('patroni.ctl.get_dcs')
def test_history(self, mock_get_dcs):
mock_get_dcs.return_value.get_cluster = Mock()
mock_get_dcs.return_value.get_cluster.return_value.history.lines = [[1, 67176, 'no recovery target specified']]
result = self.runner.invoke(ctl, ['history'])
assert 'Reason' 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')
+8 -8
View File
@@ -19,7 +19,6 @@ class TestPatroniLogger(unittest.TestCase):
logging.getLogger().handlers[:] = self._handlers
@patch('logging.FileHandler._open', Mock())
@patch('logging.Handler.close', Mock(side_effect=Exception))
def test_patroni_logger(self):
config = {
'log': {
@@ -47,13 +46,14 @@ class TestPatroniLogger(unittest.TestCase):
self.assertEqual(logger.log_handler.backupCount, config['log']['file_num'])
config['log'].pop('dir')
logger.reload_config(config['log'])
with patch.object(logging.Logger, 'makeRecord',
Mock(side_effect=[logging.LogRecord('', logging.INFO, '', 0, '', (), None), Exception])):
with patch('logging.Handler.close', Mock(side_effect=Exception)):
logger.reload_config(config['log'])
with patch.object(logging.Logger, 'makeRecord',
Mock(side_effect=[logging.LogRecord('', logging.INFO, '', 0, '', (), None), Exception])):
logging.error('test')
logging.error('test')
logging.error('test')
with patch.object(Queue, 'put_nowait', Mock(side_effect=Full)):
self.assertRaises(SystemExit, logger.shutdown)
self.assertRaises(Exception, logger.shutdown)
with patch.object(Queue, 'put_nowait', Mock(side_effect=Full)):
self.assertRaises(SystemExit, logger.shutdown)
self.assertRaises(Exception, logger.shutdown)
self.assertLessEqual(logger.queue_size, 2) # "Failed to close the old log handler" could be still in the queue
self.assertEqual(logger.records_lost, 0)