mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-08 20:18:28 -05:00
Handling the bad/lost connection of a database server.
Made backend changes for: * Taking care of the connection status in the psycopg2 driver. And, when the connection is lost, it throws a exception with 503 http status message, and connection lost information in it. * Allowing the flask application to propagate the exceptions even in the release mode. * Utilising the existing password (while reconnection, if not disconnected explicitly). * Introduced a new ajax response message 'service_unavailable' (http status code: 503), which suggests temporary service unavailable. Client (front-end) changes: * To handle the connection lost of a database server for different operations by generating proper events, and handle them properly. Removed the connection status check code from different nodes, so that - it generates the proper exception, when accessing the non-alive connection. Fixes #1387
This commit is contained in:
@@ -30,8 +30,9 @@ class DataTypeJSONEncoder(json.JSONEncoder):
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
def make_json_response(success=1, errormsg='', info='', result=None,
|
||||
data=None, status=200):
|
||||
def make_json_response(
|
||||
success=1, errormsg='', info='', result=None, data=None, status=200
|
||||
):
|
||||
"""Create a HTML response document describing the results of a request and
|
||||
containing the data."""
|
||||
doc = dict()
|
||||
@@ -44,7 +45,7 @@ def make_json_response(success=1, errormsg='', info='', result=None,
|
||||
return Response(
|
||||
response=json.dumps(doc, cls=DataTypeJSONEncoder),
|
||||
status=status,
|
||||
mimetype="text/json"
|
||||
mimetype="application/json"
|
||||
)
|
||||
|
||||
|
||||
@@ -53,7 +54,7 @@ def make_response(response=None, status=200):
|
||||
return Response(
|
||||
response=json.dumps(response, cls=DataTypeJSONEncoder),
|
||||
status=status,
|
||||
mimetype="text/json"
|
||||
mimetype="application/json"
|
||||
)
|
||||
|
||||
|
||||
@@ -120,10 +121,25 @@ def gone(errormsg=''):
|
||||
)
|
||||
|
||||
|
||||
def not_implemented(errormsg=_('Not implemented.')):
|
||||
def not_implemented(errormsg=_('Not implemented.'), info='', result=None, data=None):
|
||||
"""Create a response with HTTP status code 501 - Not Implemented."""
|
||||
return make_json_response(
|
||||
status=501,
|
||||
success=0,
|
||||
errormsg=errormsg
|
||||
errormsg=errormsg,
|
||||
info=info,
|
||||
result=result,
|
||||
data=data
|
||||
)
|
||||
|
||||
|
||||
def service_unavailable(errormsg=_("Service Unavailable"), info='', result=None, data=None):
|
||||
"""Create a response with HTTP status code 503 - Server Unavailable."""
|
||||
return make_json_response(
|
||||
status=503,
|
||||
success=0,
|
||||
errormsg=errormsg,
|
||||
info=info,
|
||||
result=result,
|
||||
data=data
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ from psycopg2.extensions import adapt
|
||||
|
||||
import config
|
||||
from pgadmin.model import Server, User
|
||||
from pgadmin.utils.exception import ConnectionLost
|
||||
from .keywords import ScanKeyword
|
||||
from ..abstract import BaseDriver, BaseConnection
|
||||
from .cursor import DictCursor
|
||||
@@ -179,6 +180,7 @@ class Connection(BaseConnection):
|
||||
self.execution_aborted = False
|
||||
self.row_count = 0
|
||||
self.__notices = None
|
||||
self.password = None
|
||||
|
||||
super(Connection, self).__init__()
|
||||
|
||||
@@ -225,10 +227,13 @@ class Connection(BaseConnection):
|
||||
password = None
|
||||
mgr = self.manager
|
||||
|
||||
if 'password' in kwargs:
|
||||
encpass = kwargs['password']
|
||||
else:
|
||||
encpass = getattr(mgr, 'password', None)
|
||||
encpass = kwargs['password'] if 'password' in kwargs else None
|
||||
|
||||
if encpass is None:
|
||||
encpass = self.password or getattr(mgr, 'password', None)
|
||||
|
||||
# Reset the existing connection password
|
||||
self.password = None
|
||||
|
||||
if encpass:
|
||||
# Fetch Logged in User Details.
|
||||
@@ -239,6 +244,10 @@ class Connection(BaseConnection):
|
||||
|
||||
try:
|
||||
password = decrypt(encpass, user.password)
|
||||
|
||||
# password is in bytes, for python3 we need it in string
|
||||
if isinstance(password, bytes):
|
||||
password = password.decode()
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return False, \
|
||||
@@ -246,10 +255,6 @@ class Connection(BaseConnection):
|
||||
str(e)
|
||||
)
|
||||
|
||||
# password is in bytes, for python3 we need it in string
|
||||
if isinstance(password, bytes):
|
||||
password = password.decode()
|
||||
|
||||
try:
|
||||
if hasattr(str, 'decode'):
|
||||
database = self.db.encode('utf-8')
|
||||
@@ -401,11 +406,18 @@ WHERE
|
||||
mgr.server_cls = st
|
||||
break
|
||||
|
||||
mgr._update_password(encpass)
|
||||
mgr.update_session()
|
||||
|
||||
return True, None
|
||||
|
||||
def __cursor(self, server_cursor=False):
|
||||
if not self.conn:
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
None if self.conn_id[0:3] == u'DB:' else self.conn_id[5:]
|
||||
)
|
||||
cur = getattr(g, "{0}#{1}".format(
|
||||
self.manager.sid,
|
||||
self.conn_id.encode('utf-8')
|
||||
@@ -475,7 +487,7 @@ Attempting to reconnect to the database server (#{server_id}) for the connection
|
||||
Connection for server#{0} with database "{1}" was lost.
|
||||
Attempt to reconnect it failed with the error:
|
||||
{2}"""
|
||||
).format(self.driver.server_id, self.database, cur)
|
||||
).format(self.driver.server_id, self.db, cur)
|
||||
current_app.logger.error(msg)
|
||||
|
||||
return False, cur
|
||||
@@ -593,6 +605,12 @@ Attempt to reconnect it failed with the error:
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
if not self.connected():
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
None if self.conn_id[0:3] == u'DB:' else self.conn_id[5:]
|
||||
)
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
current_app.logger.error(
|
||||
u"Failed to execute query (execute_scalar) for the server #{server_id} - {conn_id} (Query-id: {query_id}):\nError Message:{errmsg}".format(
|
||||
@@ -694,6 +712,12 @@ Failed to execute query (execute_async) for the server #{server_id} - {conn_id}
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
if not self.connected():
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
None if self.conn_id[0:3] == u'DB:' else self.conn_id[5:]
|
||||
)
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
current_app.logger.error(u"""
|
||||
Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
@@ -733,6 +757,12 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
if not self.connected():
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
None if self.conn_id[0:3] == u'DB:' else self.conn_id[5:]
|
||||
)
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
current_app.logger.error(
|
||||
u"Failed to execute query (execute_2darray) for the server #{server_id} - {conn_id} (Query-id: {query_id}):\nError Message:{errmsg}".format(
|
||||
@@ -778,6 +808,12 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
cur.close()
|
||||
if not self.connected():
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
None if self.conn_id[0:3] == u'DB:' else self.conn_id[5:]
|
||||
)
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
current_app.logger.error(
|
||||
u"Failed to execute query (execute_dict) for the server #{server_id}- {conn_id} (Query-id: {query_id}):\nError Message:{errmsg}".format(
|
||||
@@ -867,6 +903,7 @@ Failed to reset the connection to the server due to following error:
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
self.password = None
|
||||
|
||||
def _wait(self, conn):
|
||||
"""
|
||||
@@ -942,6 +979,12 @@ Failed to reset the connection to the server due to following error:
|
||||
try:
|
||||
status = self._wait_timeout(self.conn, ASYNC_WAIT_TIMEOUT)
|
||||
except psycopg2.Error as pe:
|
||||
if cur.closed:
|
||||
raise ConnectionLost(
|
||||
self.manager.sid,
|
||||
self.db,
|
||||
self.conn_id[5:]
|
||||
)
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
return False, errmsg, None
|
||||
|
||||
@@ -1242,10 +1285,6 @@ class ServerManager(object):
|
||||
self, database=None, conn_id=None, auto_reconnect=True, did=None,
|
||||
async=None
|
||||
):
|
||||
msg_active_conn = gettext(
|
||||
"Server has no active connection. Please connect to the server."
|
||||
)
|
||||
|
||||
if database is not None:
|
||||
if hasattr(str, 'decode') and \
|
||||
not isinstance(database, unicode):
|
||||
@@ -1285,7 +1324,7 @@ WHERE db.oid = {0}""".format(did))
|
||||
))
|
||||
|
||||
if database is None:
|
||||
raise Exception(msg_active_conn)
|
||||
raise ConnectionLost(self.sid, None, None)
|
||||
|
||||
my_id = (u'CONN:{0}'.format(conn_id)) if conn_id is not None else \
|
||||
(u'DB:{0}'.format(database))
|
||||
@@ -1387,6 +1426,13 @@ WHERE db.oid = {0}""".format(did))
|
||||
|
||||
return True
|
||||
|
||||
def _update_password(self, passwd):
|
||||
self.password = passwd
|
||||
for conn_id in self.connections:
|
||||
conn = self.connections[conn_id]
|
||||
if conn.conn is not None:
|
||||
conn.password = passwd
|
||||
|
||||
def update_session(self):
|
||||
managers = session['__pgsql_server_managers'] \
|
||||
if '__pgsql_server_managers' in session else dict()
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.http import HTTP_STATUS_CODES
|
||||
from flask_babel import gettext as _
|
||||
from flask import request
|
||||
|
||||
from pgadmin.utils.ajax import service_unavailable
|
||||
|
||||
|
||||
class ConnectionLost(HTTPException):
|
||||
"""
|
||||
Exception
|
||||
"""
|
||||
|
||||
def __init__(self, _server_id, _database_name, _conn_id):
|
||||
self.sid = _server_id
|
||||
self.db = _database_name
|
||||
self.conn_id = _conn_id
|
||||
HTTPException.__init__(self)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return HTTP_STATUS_CODES.get(505, 'Service Unavailable')
|
||||
|
||||
def get_response(self, environ=None):
|
||||
return service_unavailable(
|
||||
_("Connection to the server has been lost!"),
|
||||
info="CONNECTION_LOST",
|
||||
data={
|
||||
'sid': self.sid,
|
||||
'database': self.db,
|
||||
'conn_id': self.conn_id
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user