mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added functionality to Pause/Resume replay of WAL on the database
server. Tweaked a little bit by Ashesh.
This commit is contained in:
@@ -14,7 +14,7 @@ from flask.ext.security import current_user
|
|||||||
from pgadmin.model import db, Server, ServerGroup, User
|
from pgadmin.model import db, Server, ServerGroup, User
|
||||||
from pgadmin.utils.menu import MenuItem
|
from pgadmin.utils.menu import MenuItem
|
||||||
from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \
|
from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \
|
||||||
make_response as ajax_response, internal_server_error, unauthorized
|
make_response as ajax_response, internal_server_error, unauthorized, gone
|
||||||
from pgadmin.browser.utils import PGChildNodeView
|
from pgadmin.browser.utils import PGChildNodeView
|
||||||
import traceback
|
import traceback
|
||||||
from flask.ext.babel import gettext
|
from flask.ext.babel import gettext
|
||||||
@@ -70,14 +70,22 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
connected = conn.connected()
|
connected = conn.connected()
|
||||||
if connected:
|
if connected:
|
||||||
status, in_recovery = conn.execute_scalar("""
|
status, result = conn.execute_dict("""
|
||||||
SELECT CASE WHEN usesuper
|
SELECT CASE WHEN usesuper
|
||||||
THEN pg_is_in_recovery()
|
THEN pg_is_in_recovery()
|
||||||
ELSE FALSE
|
ELSE FALSE
|
||||||
END as inrecovery
|
END as inrecovery,
|
||||||
|
CASE WHEN usesuper AND pg_is_in_recovery()
|
||||||
|
THEN pg_is_xlog_replay_paused()
|
||||||
|
ELSE FALSE
|
||||||
|
END as isreplaypaused
|
||||||
FROM pg_user WHERE usename=current_user""")
|
FROM pg_user WHERE usename=current_user""")
|
||||||
|
|
||||||
|
in_recovery = result['rows'][0]['inrecovery'];
|
||||||
|
wal_paused = result['rows'][0]['isreplaypaused']
|
||||||
else:
|
else:
|
||||||
in_recovery = None
|
in_recovery = None
|
||||||
|
wal_paused = None
|
||||||
|
|
||||||
yield self.generate_browser_node(
|
yield self.generate_browser_node(
|
||||||
"%d" % (server.id),
|
"%d" % (server.id),
|
||||||
@@ -92,7 +100,8 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||||||
version=manager.version,
|
version=manager.version,
|
||||||
db=manager.db,
|
db=manager.db,
|
||||||
user=manager.user_info if connected else None,
|
user=manager.user_info if connected else None,
|
||||||
in_recovery=in_recovery
|
in_recovery=in_recovery,
|
||||||
|
wal_pause=wal_paused
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -196,8 +205,10 @@ class ServerNode(PGChildNodeView):
|
|||||||
'connect': [{
|
'connect': [{
|
||||||
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
|
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
|
||||||
}],
|
}],
|
||||||
'change_password': [{
|
'change_password': [{'post': 'change_password'}],
|
||||||
'post': 'change_password'}]
|
'wal_replay': [{
|
||||||
|
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
|
||||||
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
def nodes(self, gid):
|
def nodes(self, gid):
|
||||||
@@ -217,6 +228,24 @@ class ServerNode(PGChildNodeView):
|
|||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
connected = conn.connected()
|
connected = conn.connected()
|
||||||
|
|
||||||
|
if connected:
|
||||||
|
status, result = conn.execute_dict("""
|
||||||
|
SELECT CASE WHEN usesuper
|
||||||
|
THEN pg_is_in_recovery()
|
||||||
|
ELSE FALSE
|
||||||
|
END as inrecovery,
|
||||||
|
CASE WHEN usesuper AND pg_is_in_recovery()
|
||||||
|
THEN pg_is_xlog_replay_paused()
|
||||||
|
ELSE FALSE
|
||||||
|
END as isreplaypaused
|
||||||
|
FROM pg_user WHERE usename=current_user""")
|
||||||
|
|
||||||
|
in_recovery = result['rows'][0]['inrecovery'];
|
||||||
|
wal_paused = result['rows'][0]['isreplaypaused']
|
||||||
|
else:
|
||||||
|
in_recovery = None
|
||||||
|
wal_paused = None
|
||||||
|
|
||||||
res.append(
|
res.append(
|
||||||
self.blueprint.generate_browser_node(
|
self.blueprint.generate_browser_node(
|
||||||
"%d" % (server.id),
|
"%d" % (server.id),
|
||||||
@@ -230,7 +259,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
server_type=manager.server_type if connected else 'pg',
|
server_type=manager.server_type if connected else 'pg',
|
||||||
version=manager.version,
|
version=manager.version,
|
||||||
db=manager.db,
|
db=manager.db,
|
||||||
user=manager.user_info if connected else None
|
user=manager.user_info if connected else None,
|
||||||
|
in_recovery=in_recovery,
|
||||||
|
wal_pause=wal_paused
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return make_json_response(result=res)
|
return make_json_response(result=res)
|
||||||
@@ -257,6 +288,24 @@ class ServerNode(PGChildNodeView):
|
|||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
connected = conn.connected()
|
connected = conn.connected()
|
||||||
|
|
||||||
|
if connected:
|
||||||
|
status, result = conn.execute_dict("""
|
||||||
|
SELECT CASE WHEN usesuper
|
||||||
|
THEN pg_is_in_recovery()
|
||||||
|
ELSE FALSE
|
||||||
|
END as inrecovery,
|
||||||
|
CASE WHEN usesuper AND pg_is_in_recovery()
|
||||||
|
THEN pg_is_xlog_replay_paused()
|
||||||
|
ELSE FALSE
|
||||||
|
END as isreplaypaused
|
||||||
|
FROM pg_user WHERE usename=current_user""")
|
||||||
|
|
||||||
|
in_recovery = result['rows'][0]['inrecovery'];
|
||||||
|
wal_paused = result['rows'][0]['isreplaypaused']
|
||||||
|
else:
|
||||||
|
in_recovery = None
|
||||||
|
wal_paused = None
|
||||||
|
|
||||||
return make_json_response(
|
return make_json_response(
|
||||||
result=self.blueprint.generate_browser_node(
|
result=self.blueprint.generate_browser_node(
|
||||||
"%d" % (server.id),
|
"%d" % (server.id),
|
||||||
@@ -270,7 +319,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
server_type=manager.server_type if connected else 'pg',
|
server_type=manager.server_type if connected else 'pg',
|
||||||
version=manager.version,
|
version=manager.version,
|
||||||
db=manager.db,
|
db=manager.db,
|
||||||
user=manager.user_info if connected else None
|
user=manager.user_info if connected else None,
|
||||||
|
in_recovery=in_recovery,
|
||||||
|
wal_pause=wal_paused
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -731,6 +782,24 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
current_app.logger.info('Connection Established for server: \
|
current_app.logger.info('Connection Established for server: \
|
||||||
%s - %s' % (server.id, server.name))
|
%s - %s' % (server.id, server.name))
|
||||||
|
# Update the recovery and wal pause option for the server if connected successfully
|
||||||
|
status, result = conn.execute_dict("""
|
||||||
|
SELECT CASE WHEN usesuper
|
||||||
|
THEN pg_is_in_recovery()
|
||||||
|
ELSE FALSE
|
||||||
|
END as inrecovery,
|
||||||
|
CASE WHEN usesuper AND pg_is_in_recovery()
|
||||||
|
THEN pg_is_xlog_replay_paused()
|
||||||
|
ELSE FALSE
|
||||||
|
END as isreplaypaused
|
||||||
|
FROM pg_user WHERE usename=current_user""")
|
||||||
|
if status:
|
||||||
|
in_recovery = result['rows'][0]['inrecovery'];
|
||||||
|
wal_paused = result['rows'][0]['isreplaypaused']
|
||||||
|
else:
|
||||||
|
in_recovery = None
|
||||||
|
wal_paused = None
|
||||||
|
|
||||||
return make_json_response(
|
return make_json_response(
|
||||||
success=1,
|
success=1,
|
||||||
info=gettext("Server connected."),
|
info=gettext("Server connected."),
|
||||||
@@ -742,7 +811,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
'type': manager.server_type,
|
'type': manager.server_type,
|
||||||
'version': manager.version,
|
'version': manager.version,
|
||||||
'db': manager.db,
|
'db': manager.db,
|
||||||
'user': manager.user_info
|
'user': manager.user_info,
|
||||||
|
'in_recovery': in_recovery,
|
||||||
|
'wal_pause': wal_paused
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -936,4 +1007,81 @@ class ServerNode(PGChildNodeView):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return internal_server_error(errormsg=str(e))
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
def wal_replay(self, sid, pause=True):
|
||||||
|
"""
|
||||||
|
Utility function for wal_replay for resume/pause.
|
||||||
|
"""
|
||||||
|
server = Server.query.filter_by(
|
||||||
|
user_id=current_user.id, id=sid
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if server is None:
|
||||||
|
return make_json_response(
|
||||||
|
success=0,
|
||||||
|
errormsg=gettext("Could not find the required server.")
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
|
conn = manager.connection()
|
||||||
|
|
||||||
|
# Execute SQL to pause or resume WAL replay
|
||||||
|
if conn.connected():
|
||||||
|
if pause:
|
||||||
|
status, res = conn.execute_scalar(
|
||||||
|
"SELECT pg_xlog_replay_pause();"
|
||||||
|
)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(
|
||||||
|
errormsg=str(res)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status, res = conn.execute_scalar(
|
||||||
|
"SELECT pg_xlog_replay_resume();"
|
||||||
|
)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(
|
||||||
|
errormsg=str(res)
|
||||||
|
)
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info=gettext('WAL replay paused'),
|
||||||
|
data={'in_recovery': True, 'wal_pause': pause}
|
||||||
|
)
|
||||||
|
return gone(errormsg=_('Please connect the server!'))
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(
|
||||||
|
'WAL replay pause/resume failed'
|
||||||
|
)
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
def resume_wal_replay(self, gid, sid):
|
||||||
|
"""
|
||||||
|
This method will resume WAL replay
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server group ID
|
||||||
|
sid: Server ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
return self.wal_replay(sid, False)
|
||||||
|
|
||||||
|
def pause_wal_replay(self, gid, sid):
|
||||||
|
"""
|
||||||
|
This method will pause WAL replay
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gid: Server group ID
|
||||||
|
sid: Server ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
return self.wal_replay(sid, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ServerNode.register_node_view(blueprint)
|
ServerNode.register_node_view(blueprint)
|
||||||
|
|||||||
@@ -53,7 +53,17 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||||||
applies: ['file'], callback: 'change_password',
|
applies: ['file'], callback: 'change_password',
|
||||||
label: '{{ _('Change Password...') }}',
|
label: '{{ _('Change Password...') }}',
|
||||||
icon: 'fa fa-lock', enable : 'is_connected'
|
icon: 'fa fa-lock', enable : 'is_connected'
|
||||||
}]);
|
},{
|
||||||
|
name: 'wal_replay_pause', node: 'server', module: this,
|
||||||
|
applies: ['tools', 'context'], callback: 'pause_wal_replay',
|
||||||
|
category: 'wal_replay_pause', priority: 8, label: '{{ _('Pause replay of WAL') }}',
|
||||||
|
icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled'
|
||||||
|
},{
|
||||||
|
name: 'wal_replay_resume', node: 'server', module: this,
|
||||||
|
applies: ['tools', 'context'], callback: 'resume_wal_replay',
|
||||||
|
category: 'wal_replay_resume', priority: 9, label: '{{ _('Resume replay of WAL') }}',
|
||||||
|
icon: 'fa fa-play-circle', enable : 'wal_resume_enabled'
|
||||||
|
}]);
|
||||||
|
|
||||||
pgBrowser.messages['PRIV_GRANTEE_NOT_SPECIFIED'] =
|
pgBrowser.messages['PRIV_GRANTEE_NOT_SPECIFIED'] =
|
||||||
'{{ _('A grantee must be selected.') }}';
|
'{{ _('A grantee must be selected.') }}';
|
||||||
@@ -83,6 +93,26 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
wal_pause_enabled: function(node) {
|
||||||
|
// Must be connected & is Super user & in Recovery mode
|
||||||
|
if (node && node._type == "server" &&
|
||||||
|
node.connected && node.user.is_superuser
|
||||||
|
&& node.in_recovery == true
|
||||||
|
&& node.wal_pause == false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
wal_resume_enabled: function(node) {
|
||||||
|
// Must be connected & is Super user & in Recovery mode
|
||||||
|
if (node && node._type == "server" &&
|
||||||
|
node.connected && node.user.is_superuser
|
||||||
|
&& node.in_recovery == true
|
||||||
|
&& node.wal_pause == true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
/* Connect the server */
|
/* Connect the server */
|
||||||
connect_server: function(args){
|
connect_server: function(args){
|
||||||
@@ -421,6 +451,88 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||||||
|
|
||||||
alertify.changeServerPassword(d).resizeTo('40%','52%');
|
alertify.changeServerPassword(d).resizeTo('40%','52%');
|
||||||
return false;
|
return false;
|
||||||
|
},
|
||||||
|
/* Pause WAL Replay */
|
||||||
|
pause_wal_replay: function(args) {
|
||||||
|
var input = args || {};
|
||||||
|
obj = this,
|
||||||
|
t = pgBrowser.tree,
|
||||||
|
i = input.item || t.selected(),
|
||||||
|
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||||
|
|
||||||
|
if (!d)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var data = d;
|
||||||
|
$.ajax({
|
||||||
|
url: obj.generate_url(i, 'wal_replay' , d, true),
|
||||||
|
type:'DELETE',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
if (res.success == 1) {
|
||||||
|
alertify.success(res.info);
|
||||||
|
t.itemData(i).wal_pause=res.data.wal_pause;
|
||||||
|
t.unload(i);
|
||||||
|
t.setInode(i);
|
||||||
|
t.deselect(i);
|
||||||
|
// Fetch updated data from server
|
||||||
|
setTimeout(function() {
|
||||||
|
t.select(i);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
try {
|
||||||
|
var err = $.parseJSON(xhr.responseText);
|
||||||
|
if (err.success == 0) {
|
||||||
|
msg = S(err.errormsg).value();
|
||||||
|
alertify.error(err.errormsg);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
t.unload(i);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/* Resume WAL Replay */
|
||||||
|
resume_wal_replay: function(args) {
|
||||||
|
var input = args || {};
|
||||||
|
obj = this,
|
||||||
|
t = pgBrowser.tree,
|
||||||
|
i = input.item || t.selected(),
|
||||||
|
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||||
|
|
||||||
|
if (!d)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var data = d;
|
||||||
|
$.ajax({
|
||||||
|
url: obj.generate_url(i, 'wal_replay' , d, true),
|
||||||
|
type:'PUT',
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
if (res.success == 1) {
|
||||||
|
alertify.success(res.info);
|
||||||
|
t.itemData(i).wal_pause=res.data.wal_pause;
|
||||||
|
t.unload(i);
|
||||||
|
t.setInode(i);
|
||||||
|
t.deselect(i);
|
||||||
|
// Fetch updated data from server
|
||||||
|
setTimeout(function() {
|
||||||
|
t.select(i);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
try {
|
||||||
|
var err = $.parseJSON(xhr.responseText);
|
||||||
|
if (err.success == 0) {
|
||||||
|
msg = S(err.errormsg).value();
|
||||||
|
alertify.error(err.errormsg);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
t.unload(i);
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
model: pgAdmin.Browser.Node.Model.extend({
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
@@ -555,6 +667,11 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||||||
data.icon = res.data.icon;
|
data.icon = res.data.icon;
|
||||||
tree.addIcon(item, {icon: data.icon});
|
tree.addIcon(item, {icon: data.icon});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update 'in_recovery' and 'wal_pause' options at server node
|
||||||
|
tree.itemData(item).in_recovery=res.data.in_recovery;
|
||||||
|
tree.itemData(item).wal_pause=res.data.wal_pause;
|
||||||
|
|
||||||
_.extend(data, res.data);
|
_.extend(data, res.data);
|
||||||
|
|
||||||
var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {};
|
var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {};
|
||||||
|
|||||||
Reference in New Issue
Block a user