Avoid an unhandled exception in the API thread.

When receiving a failover request with no data or
non-JSON data, emit a message to the client instead
of crashing.
This commit is contained in:
Oleksii Kliukin
2016-03-04 19:21:14 +01:00
parent 45cb3f9696
commit aa844b63d0
2 changed files with 38 additions and 24 deletions
+34 -24
View File
@@ -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')
+4
View File
@@ -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)