diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a79f1d4..566aa60 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -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 @@ -193,6 +202,8 @@ class ServerNode(PGChildNodeView): [{'get': 'reload_configuration'}], 'restore_point': [{'post': 'create_restore_point'}], + 'wal_replay': + [{'post': 'pause_resume_wal_replay'}], 'connect': [{ 'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect' }] @@ -215,6 +226,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), @@ -228,7 +257,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) @@ -255,6 +286,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), @@ -268,7 +317,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 ) ) @@ -729,6 +780,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."), @@ -740,7 +809,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 } ) @@ -838,4 +909,65 @@ class ServerNode(PGChildNodeView): ) return internal_server_error(errormsg=str(e)) + def pause_resume_wal_replay(self, gid, sid): + """ + This method will pause/resume WAL replay + + Args: + gid: Server group ID + sid: Server ID + + Returns: + None + """ + 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: + data = request.form if request.form else json.loads(request.data.decode()) + + # Convert str 'true' to boolean type + is_wal_pause = json.loads(data['wal_pause']) + + 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 is_wal_pause: + status, res = conn.execute_scalar("SELECT pg_xlog_replay_pause();") + 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': True } + ) + 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 resumed'), + data={'in_recovery': True, 'wal_pause': False } + ) + except Exception as e: + current_app.logger.error( + 'WAL replay pause/resume failed' + ) + return internal_server_error(errormsg=str(e)) + + 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 2e8105b..ec8c4ef 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js @@ -48,7 +48,17 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 7, label: '{{ _('Add named restore point') }}', icon: 'fa fa-anchor', enable : 'is_applicable' - }]); + },{ + 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.') }}'; @@ -78,6 +88,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){ @@ -259,7 +289,91 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { evt.cancel = false; } ).set({'title':'Restore point name'}); - } + }, + /* 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:'POST', + data: {'wal_pause' : true}, + 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:'POST', + data: {'wal_pause' : false}, + 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({ defaults: { @@ -393,6 +507,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 || {};