diff --git a/web/migrations/versions/b5b87fdfcb30_.py b/web/migrations/versions/b5b87fdfcb30_.py new file mode 100644 index 000000000..ca2da078b --- /dev/null +++ b/web/migrations/versions/b5b87fdfcb30_.py @@ -0,0 +1,33 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Added state of the utility process + +Revision ID: b5b87fdfcb30 +Revises: ece2e76bf60e +Create Date: 2018-10-24 12:37:59.487969 + +""" +from pgadmin.model import db + +# revision identifiers, used by Alembic. +revision = 'b5b87fdfcb30' +down_revision = 'ece2e76bf60e' +branch_labels = None +depends_on = None + + +def upgrade(): + db.engine.execute( + 'ALTER TABLE process ADD COLUMN process_state INTEGER DEFAULT 0' + ) + + +def downgrade(): + pass diff --git a/web/pgadmin/misc/bgprocess/processes.py b/web/pgadmin/misc/bgprocess/processes.py index a8856f8f0..bb5efb260 100644 --- a/web/pgadmin/misc/bgprocess/processes.py +++ b/web/pgadmin/misc/bgprocess/processes.py @@ -36,6 +36,11 @@ if IS_PY2: else: from io import StringIO +PROCESS_NOT_STARTED = 0 +PROCESS_STARTED = 1 +PROCESS_FINISHED = 2 +PROCESS_TERMINATED = 3 + def get_current_time(format='%Y-%m-%d %H:%M:%S.%f %z'): """ @@ -113,6 +118,8 @@ class BatchProcess(object): self.etime = p.end_time # Exit code self.ecode = p.exit_code + # Process State + self.process_state = p.process_state def _create_process(self, _desc, _cmd, _args): ctime = get_current_time(format='%y%m%d%H%M%S%f') @@ -166,6 +173,8 @@ class BatchProcess(object): self.etime = None # Exit code self.ecode = None + # Process State + self.process_state = PROCESS_NOT_STARTED # Arguments self.args = _args @@ -393,6 +402,14 @@ class BatchProcess(object): p.start_time = p.end_time = get_current_time() if not p.exit_code: p.exit_code = self.ecode + p.process_state = PROCESS_FINISHED + db.session.commit() + else: + # Update the process state to "Started" + p = Process.query.filter_by( + pid=self.id, user_id=current_user.id + ).first() + p.process_state = PROCESS_STARTED db.session.commit() def status(self, out=0, err=0): @@ -480,7 +497,8 @@ class BatchProcess(object): return { 'start_time': self.stime, 'exit_code': self.ecode, - 'execution_time': execution_time + 'execution_time': execution_time, + 'process_state': self.process_state } return { @@ -496,7 +514,8 @@ class BatchProcess(object): }, 'start_time': self.stime, 'exit_code': self.ecode, - 'execution_time': execution_time + 'execution_time': execution_time, + 'process_state': self.process_state } @staticmethod @@ -597,7 +616,8 @@ class BatchProcess(object): 'etime': p.end_time, 'exit_code': p.exit_code, 'acknowledge': p.acknowledge, - 'execution_time': execution_time + 'execution_time': execution_time, + 'process_state': p.process_state }) if changed: @@ -679,6 +699,9 @@ class BatchProcess(object): try: process = psutil.Process(p.utility_pid) process.terminate() + # Update the process state to "Terminated" + p.process_state = PROCESS_TERMINATED + db.session.commit() except psutil.Error as e: current_app.logger.warning( _("Unable to kill the background process '{0}'").format( diff --git a/web/pgadmin/misc/bgprocess/static/js/bgprocess.js b/web/pgadmin/misc/bgprocess/static/js/bgprocess.js index c88cc5533..bdbb04377 100644 --- a/web/pgadmin/misc/bgprocess/static/js/bgprocess.js +++ b/web/pgadmin/misc/bgprocess/static/js/bgprocess.js @@ -29,7 +29,7 @@ define('misc.bgprocess', [ details: false, notify: (_.isUndefined(notify) || notify), curr_status: null, - state: 0, // 0: NOT Started, 1: Started, 2: Finished + state: 0, // 0: NOT Started, 1: Started, 2: Finished, 3: Terminated completed: false, id: info['id'], @@ -124,6 +124,9 @@ define('misc.bgprocess', [ if ('exit_code' in data) self.exit_code = data.exit_code; + if ('process_state' in data) + self.state = data.process_state; + if ('out' in data) { self.out = data.out && data.out.pos; @@ -183,7 +186,9 @@ define('misc.bgprocess', [ } if (!_.isNull(self.exit_code)) { - if (self.exit_code == 0) { + if (self.state === 3) { + self.curr_status = gettext('Terminated by user.'); + } else if (self.exit_code == 0) { self.curr_status = gettext('Successfully completed.'); } else { self.curr_status = S( @@ -349,18 +354,19 @@ define('misc.bgprocess', [ self.curr_status ); - if (self.exit_code === 0) { - $status_bar.addClass('bg-success'); - } else if (self.exit_code == 1) { - $status_bar.addClass('bg-failed'); - } - // Enable/Disable stop process button var $btn_stop_process = $(self.container.find('.bg-process-stop')); if (isNaN(parseInt(self.exit_code))) { - $btn_stop_process.removeClass('disabled'); + $btn_stop_process.attr('disabled', false); } else { - $btn_stop_process.addClass('disabled'); + $btn_stop_process.attr('disabled', true); + if (self.exit_code === 0 && self.state != 3) { + $status_bar.addClass('bg-success'); + $status_bar.removeClass('bg-failed'); + } else { + $status_bar.addClass('bg-failed'); + $status_bar.removeClass('bg-success'); + } } } else { self.show_detailed_view.apply(self); @@ -382,11 +388,7 @@ define('misc.bgprocess', [ } var container = panel.$container, - status_class = ( - (self.exit_code === 0) ? - 'bg-bgprocess-success' : (self.exit_code == 1) ? - 'bg-bgprocess-failed' : '' - ), + status_class = '', $logs = container.find('.bg-process-watcher'), $header = container.find('.bg-process-details'), $footer = container.find('.bg-process-footer'), @@ -394,9 +396,14 @@ define('misc.bgprocess', [ // Enable/Disable stop process button if (isNaN(parseInt(self.exit_code))) { - $btn_stop_process.removeClass('disabled'); + $btn_stop_process.attr('disabled', false); } else { - $btn_stop_process.addClass('disabled'); + $btn_stop_process.attr('disabled', true); + if (self.exit_code === 0 && self.state != 3) { + status_class = 'bg-bgprocess-success'; + } else { + status_class = 'bg-bgprocess-failed'; + } } // On Click event to stop the process. @@ -483,6 +490,8 @@ define('misc.bgprocess', [ stop_process: function() { var self = this; + // Set the state to terminated. + self.state = 3; $.ajax({ type: 'PUT', timeout: 30000, diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 57a6360ab..db7121764 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy # ########################################################################## -SCHEMA_VERSION = 20 +SCHEMA_VERSION = 21 ########################################################################## # @@ -259,6 +259,7 @@ class Process(db.Model): exit_code = db.Column(db.Integer(), nullable=True) acknowledge = db.Column(db.String(), nullable=True) utility_pid = db.Column(db.Integer, nullable=False) + process_state = db.Column(db.Integer, nullable=False) class Keys(db.Model): diff --git a/web/pgadmin/tools/backup/tests/test_batch_process.py b/web/pgadmin/tools/backup/tests/test_batch_process.py index 25342d4ef..5e0883510 100644 --- a/web/pgadmin/tools/backup/tests/test_batch_process.py +++ b/web/pgadmin/tools/backup/tests/test_batch_process.py @@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator from pickle import dumps, loads if sys.version_info < (3, 3): - from mock import patch + from mock import patch, MagicMock else: - from unittest.mock import patch + from unittest.mock import patch, MagicMock class BatchProcessTest(BaseTestGenerator): @@ -141,6 +141,7 @@ class BatchProcessTest(BaseTestGenerator): )) db_mock.session.add.side_effect = db_session_add_mock + db_mock.session.commit = MagicMock(return_value=True) get_server_details_mock.return_value = \ self.class_params['name'],\ @@ -165,12 +166,29 @@ class BatchProcessTest(BaseTestGenerator): self.assertTrue(db_mock.session.add.called) # Check start method - self._check_start(popen_mock, p) + self._check_start(popen_mock, p, backup_obj) # Check list method self._check_list(p, backup_obj) - def _check_start(self, popen_mock, p): + @patch('pgadmin.misc.bgprocess.processes.Process') + def _check_start(self, popen_mock, p, backup_obj, process_mock): + class TestMockProcess(): + def __init__(self, desc, args, cmd): + self.pid = 1 + self.exit_code = 1 + self.start_time = '2018-04-17 06:18:56.315445 +0000' + self.end_time = None + self.desc = dumps(desc) + self.arguments = " ".join(args) + self.command = cmd + self.acknowledge = None + self.process_state = 0 + + mock_result = process_mock.query.filter_by.return_value + mock_result.first.return_value = TestMockProcess( + backup_obj, self.class_params['args'], self.class_params['cmd']) + cmd_test = self.class_params['cmd'] assert_true = self.assertTrue @@ -202,6 +220,7 @@ class BatchProcessTest(BaseTestGenerator): self.arguments = " ".join(args) self.command = cmd self.acknowledge = None + self.process_state = 0 process_mock.query.filter_by.return_value = [ TestMockProcess(backup_obj, diff --git a/web/pgadmin/tools/maintenance/tests/test_batch_process_maintenance.py b/web/pgadmin/tools/maintenance/tests/test_batch_process_maintenance.py index 3700d9622..96b28d5de 100644 --- a/web/pgadmin/tools/maintenance/tests/test_batch_process_maintenance.py +++ b/web/pgadmin/tools/maintenance/tests/test_batch_process_maintenance.py @@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator from pickle import dumps, loads if sys.version_info < (3, 3): - from mock import patch + from mock import patch, MagicMock else: - from unittest.mock import patch + from unittest.mock import patch, MagicMock class BatchProcessTest(BaseTestGenerator): @@ -89,6 +89,7 @@ class BatchProcessTest(BaseTestGenerator): mock_result.first.return_value = mock_obj db_mock.session.add.side_effect = db_session_add_mock + db_mock.session.commit = MagicMock(return_value=True) maintenance_obj = Message( self.class_params['sid'], @@ -106,12 +107,30 @@ class BatchProcessTest(BaseTestGenerator): self.assertTrue(db_mock.session.add.called) # Check start method - self._check_start(popen_mock, p) + self._check_start(popen_mock, p, maintenance_obj) # Check list method self._check_list(p, maintenance_obj) - def _check_start(self, popen_mock, p): + @patch('pgadmin.misc.bgprocess.processes.Process') + def _check_start(self, popen_mock, p, maintenance_obj, process_mock): + class TestMockProcess(): + def __init__(self, desc, args, cmd): + self.pid = 1 + self.exit_code = 1 + self.start_time = '2018-04-17 06:18:56.315445 +0000' + self.end_time = None + self.desc = dumps(desc) + self.arguments = " ".join(args) + self.command = cmd + self.acknowledge = None + self.process_state = 0 + + mock_result = process_mock.query.filter_by.return_value + mock_result.first.return_value = TestMockProcess( + maintenance_obj, self.class_params['args'], + self.class_params['cmd']) + cmd_test = self.class_params['cmd'] assert_true = self.assertTrue @@ -143,6 +162,7 @@ class BatchProcessTest(BaseTestGenerator): self.arguments = " ".join(args) self.command = cmd self.acknowledge = None + self.process_state = 0 process_mock.query.filter_by.return_value = [ TestMockProcess(maintenance_obj, diff --git a/web/pgadmin/tools/restore/tests/test_batch_process.py b/web/pgadmin/tools/restore/tests/test_batch_process.py index 8bc5ede97..d2bf61c92 100644 --- a/web/pgadmin/tools/restore/tests/test_batch_process.py +++ b/web/pgadmin/tools/restore/tests/test_batch_process.py @@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator from pickle import dumps, loads if sys.version_info < (3, 3): - from mock import patch + from mock import patch, MagicMock else: - from unittest.mock import patch + from unittest.mock import patch, MagicMock class BatchProcessTest(BaseTestGenerator): @@ -89,6 +89,7 @@ class BatchProcessTest(BaseTestGenerator): self.class_params['port'] db_mock.session.add.side_effect = db_session_add_mock + db_mock.session.commit = MagicMock(return_value=True) restore_obj = RestoreMessage( self.class_params['sid'], @@ -106,12 +107,30 @@ class BatchProcessTest(BaseTestGenerator): self.assertTrue(db_mock.session.add.called) # Check start method - self._check_start(popen_mock, p) + self._check_start(popen_mock, p, restore_obj) # Check list method self._check_list(p, restore_obj) - def _check_start(self, popen_mock, p): + @patch('pgadmin.misc.bgprocess.processes.Process') + def _check_start(self, popen_mock, p, restore_obj, process_mock): + class TestMockProcess(): + def __init__(self, desc, args, cmd): + self.pid = 1 + self.exit_code = 1 + self.start_time = '2018-04-17 06:18:56.315445 +0000' + self.end_time = None + self.desc = dumps(desc) + self.arguments = " ".join(args) + self.command = cmd + self.acknowledge = None + self.process_state = 0 + + mock_result = process_mock.query.filter_by.return_value + mock_result.first.return_value = TestMockProcess( + restore_obj, self.class_params['args'], + self.class_params['cmd']) + cmd_test = self.class_params['cmd'] assert_true = self.assertTrue @@ -143,6 +162,7 @@ class BatchProcessTest(BaseTestGenerator): self.arguments = " ".join(args) self.command = cmd self.acknowledge = None + self.process_state = 0 process_mock.query.filter_by.return_value = [ TestMockProcess(restore_obj,