Added functionality to Pause/Resume replay of WAL on the database

server.

Tweaked a little bit by Ashesh.
This commit is contained in:
Neel Patel
2016-05-14 01:39:39 +05:30
committed by Ashesh Vashi
parent 14839bf5de
commit 4dc7f84761
2 changed files with 275 additions and 10 deletions

View File

@@ -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)

View File

@@ -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 || {};