From 4dc7f84761d85c94657a17533ef34e96550c3998 Mon Sep 17 00:00:00 2001 From: Neel Patel Date: Sat, 14 May 2016 01:39:39 +0530 Subject: [PATCH] Added functionality to Pause/Resume replay of WAL on the database server. Tweaked a little bit by Ashesh. --- .../browser/server_groups/servers/__init__.py | 166 +++++++++++++++++- .../servers/templates/servers/servers.js | 119 ++++++++++++- 2 files changed, 275 insertions(+), 10 deletions(-) diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 9b8b6b7e7..0c7bf3946 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -14,7 +14,7 @@ from flask.ext.security import current_user from pgadmin.model import db, Server, ServerGroup, User from pgadmin.utils.menu import MenuItem 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 import traceback from flask.ext.babel import gettext @@ -70,14 +70,22 @@ class ServerModule(sg.ServerGroupPluginModule): conn = manager.connection() connected = conn.connected() if connected: - status, in_recovery = conn.execute_scalar(""" + status, result = conn.execute_dict(""" SELECT CASE WHEN usesuper THEN pg_is_in_recovery() 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""") + + in_recovery = result['rows'][0]['inrecovery']; + wal_paused = result['rows'][0]['isreplaypaused'] else: in_recovery = None + wal_paused = None yield self.generate_browser_node( "%d" % (server.id), @@ -92,7 +100,8 @@ class ServerModule(sg.ServerGroupPluginModule): version=manager.version, db=manager.db, user=manager.user_info if connected else None, - in_recovery=in_recovery + in_recovery=in_recovery, + wal_pause=wal_paused ) @property @@ -196,8 +205,10 @@ class ServerNode(PGChildNodeView): 'connect': [{ 'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect' }], - 'change_password': [{ - 'post': 'change_password'}] + 'change_password': [{'post': 'change_password'}], + 'wal_replay': [{ + 'delete': 'pause_wal_replay', 'put': 'resume_wal_replay' + }] }) def nodes(self, gid): @@ -217,6 +228,24 @@ class ServerNode(PGChildNodeView): conn = manager.connection() 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( self.blueprint.generate_browser_node( "%d" % (server.id), @@ -230,7 +259,9 @@ class ServerNode(PGChildNodeView): server_type=manager.server_type if connected else 'pg', version=manager.version, 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) @@ -257,6 +288,24 @@ class ServerNode(PGChildNodeView): conn = manager.connection() 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( result=self.blueprint.generate_browser_node( "%d" % (server.id), @@ -270,7 +319,9 @@ class ServerNode(PGChildNodeView): server_type=manager.server_type if connected else 'pg', version=manager.version, 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: \ %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( success=1, info=gettext("Server connected."), @@ -742,7 +811,9 @@ class ServerNode(PGChildNodeView): 'type': manager.server_type, 'version': manager.version, '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: 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) diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js index 16831e6c8..ab0bce2d6 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js @@ -53,7 +53,17 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { applies: ['file'], callback: 'change_password', label: '{{ _('Change Password...') }}', 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'] = '{{ _('A grantee must be selected.') }}'; @@ -83,6 +93,26 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { } 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: { /* Connect the server */ connect_server: function(args){ @@ -421,6 +451,88 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { alertify.changeServerPassword(d).resizeTo('40%','52%'); 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({ @@ -555,6 +667,11 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { data.icon = res.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); var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {};