diff --git a/patroni/api.py b/patroni/api.py index ba18b333..1186321f 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -174,38 +174,48 @@ class RestApiHandler(BaseHTTPRequestHandler): @check_auth def do_POST_failover(self): content_length = int(self.headers.get('content-length', 0)) - request = json.loads(self.rfile.read(content_length).decode('utf-8')) + try: + request = json.loads(self.rfile.read(content_length).decode('utf-8')) + except ValueError: + request = {} leader = request.get('leader') member = request.get('member') cluster = self.server.patroni.ha.dcs.get_cluster() status_code = 500 + logger.info("received failover request with leader {0} member {1} scheduled_at {2}". + format(leader, member, request.get("scheduled_at"))) + data = b'' - if request.get('scheduled_at'): - try: - scheduled_at = dateutil.parser.parse(request['scheduled_at']) - if scheduled_at.tzinfo is None: - data = b'Timezone information is mandatory for scheduled_at' - status_code = 400 - elif scheduled_at < datetime.datetime.now(pytz.utc): - data = b'Cannot schedule failover in the past' + if leader or member: + if request.get('scheduled_at'): + try: + scheduled_at = dateutil.parser.parse(request['scheduled_at']) + if scheduled_at.tzinfo is None: + data = b'Timezone information is mandatory for scheduled_at' + status_code = 400 + elif scheduled_at < datetime.datetime.now(pytz.utc): + data = b'Cannot schedule failover in the past' + status_code = 422 + elif self.server.patroni.dcs.manual_failover(leader, member, scheduled_at): + data = b'Failover scheduled' + status_code = 200 + except (ValueError, TypeError): + logger.exception('Invalid scheduled failover time: {}'.format(request['scheduled_at'])) + data = b'Unable to parse scheduled timestamp. It should be in an unambiguous format, e.g. ISO 8601' status_code = 422 - elif self.server.patroni.dcs.manual_failover(leader, member, scheduled_at): - data = b'Failover scheduled' - status_code = 200 - except (ValueError, TypeError): - logger.exception('Invalid scheduled failover time: {}'.format(request['scheduled_at'])) - data = b'Unable to parse scheduled timestamp. It should be in an unambiguous format, e.g. ISO 8601' - status_code = 422 + else: + data = self.is_failover_possible(cluster, leader, member) + if not data: + if not self.server.patroni.dcs.manual_failover(leader, member): + data = b'failed to write failover key into DCS' + status_code = 503 + else: + self.server.patroni.dcs.event.set() + status_code, data = self.poll_failover_result(cluster.leader and cluster.leader.name, member) else: - data = self.is_failover_possible(cluster, leader, member) - if not data: - if not self.server.patroni.dcs.manual_failover(leader, member): - data = b'failed to write failover key into DCS' - status_code = 503 - else: - self.server.patroni.dcs.event.set() - status_code, data = self.poll_failover_result(cluster.leader and cluster.leader.name, member) + status_code = 400 + data = b'No values given for required parameters leader and member' self.send_response(status_code) self.send_header('Content-Type', 'text/html') diff --git a/tests/test_api.py b/tests/test_api.py index 1be0f96c..0d6dd151 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -153,6 +153,10 @@ class TestRestApiHandler(unittest.TestCase): @patch.object(MockHa, 'dcs') def test_do_POST_failover(self, dcs): cluster = dcs.get_cluster.return_value + + request = b'POST /failover HTTP/1.0\nAuthorization: Basic dGVzdDp0ZXN0\n' +\ + b'Content-Length: 0\n\n' + MockRestApiServer(RestApiHandler, request) request = b'POST /failover HTTP/1.0\nAuthorization: Basic dGVzdDp0ZXN0\n' +\ b'Content-Length: 25\n\n{"leader": "postgresql1"}' MockRestApiServer(RestApiHandler, request)