1) Fixed process watcher status message when user has stopped the process.

2) Saved the process state in sqlite database.
This commit is contained in:
Akshay Joshi
2018-10-25 17:03:34 +05:30
parent 091aa62084
commit 6ddab02769
7 changed files with 158 additions and 33 deletions

View File

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

View File

@@ -36,6 +36,11 @@ if IS_PY2:
else: else:
from io import StringIO 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'): 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 self.etime = p.end_time
# Exit code # Exit code
self.ecode = p.exit_code self.ecode = p.exit_code
# Process State
self.process_state = p.process_state
def _create_process(self, _desc, _cmd, _args): def _create_process(self, _desc, _cmd, _args):
ctime = get_current_time(format='%y%m%d%H%M%S%f') ctime = get_current_time(format='%y%m%d%H%M%S%f')
@@ -166,6 +173,8 @@ class BatchProcess(object):
self.etime = None self.etime = None
# Exit code # Exit code
self.ecode = None self.ecode = None
# Process State
self.process_state = PROCESS_NOT_STARTED
# Arguments # Arguments
self.args = _args self.args = _args
@@ -393,6 +402,14 @@ class BatchProcess(object):
p.start_time = p.end_time = get_current_time() p.start_time = p.end_time = get_current_time()
if not p.exit_code: if not p.exit_code:
p.exit_code = self.ecode 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() db.session.commit()
def status(self, out=0, err=0): def status(self, out=0, err=0):
@@ -480,7 +497,8 @@ class BatchProcess(object):
return { return {
'start_time': self.stime, 'start_time': self.stime,
'exit_code': self.ecode, 'exit_code': self.ecode,
'execution_time': execution_time 'execution_time': execution_time,
'process_state': self.process_state
} }
return { return {
@@ -496,7 +514,8 @@ class BatchProcess(object):
}, },
'start_time': self.stime, 'start_time': self.stime,
'exit_code': self.ecode, 'exit_code': self.ecode,
'execution_time': execution_time 'execution_time': execution_time,
'process_state': self.process_state
} }
@staticmethod @staticmethod
@@ -597,7 +616,8 @@ class BatchProcess(object):
'etime': p.end_time, 'etime': p.end_time,
'exit_code': p.exit_code, 'exit_code': p.exit_code,
'acknowledge': p.acknowledge, 'acknowledge': p.acknowledge,
'execution_time': execution_time 'execution_time': execution_time,
'process_state': p.process_state
}) })
if changed: if changed:
@@ -679,6 +699,9 @@ class BatchProcess(object):
try: try:
process = psutil.Process(p.utility_pid) process = psutil.Process(p.utility_pid)
process.terminate() process.terminate()
# Update the process state to "Terminated"
p.process_state = PROCESS_TERMINATED
db.session.commit()
except psutil.Error as e: except psutil.Error as e:
current_app.logger.warning( current_app.logger.warning(
_("Unable to kill the background process '{0}'").format( _("Unable to kill the background process '{0}'").format(

View File

@@ -29,7 +29,7 @@ define('misc.bgprocess', [
details: false, details: false,
notify: (_.isUndefined(notify) || notify), notify: (_.isUndefined(notify) || notify),
curr_status: null, 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, completed: false,
id: info['id'], id: info['id'],
@@ -124,6 +124,9 @@ define('misc.bgprocess', [
if ('exit_code' in data) if ('exit_code' in data)
self.exit_code = data.exit_code; self.exit_code = data.exit_code;
if ('process_state' in data)
self.state = data.process_state;
if ('out' in data) { if ('out' in data) {
self.out = data.out && data.out.pos; self.out = data.out && data.out.pos;
@@ -183,7 +186,9 @@ define('misc.bgprocess', [
} }
if (!_.isNull(self.exit_code)) { 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.'); self.curr_status = gettext('Successfully completed.');
} else { } else {
self.curr_status = S( self.curr_status = S(
@@ -349,18 +354,19 @@ define('misc.bgprocess', [
self.curr_status 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 // Enable/Disable stop process button
var $btn_stop_process = $(self.container.find('.bg-process-stop')); var $btn_stop_process = $(self.container.find('.bg-process-stop'));
if (isNaN(parseInt(self.exit_code))) { if (isNaN(parseInt(self.exit_code))) {
$btn_stop_process.removeClass('disabled'); $btn_stop_process.attr('disabled', false);
} else { } 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 { } else {
self.show_detailed_view.apply(self); self.show_detailed_view.apply(self);
@@ -382,11 +388,7 @@ define('misc.bgprocess', [
} }
var container = panel.$container, var container = panel.$container,
status_class = ( status_class = '',
(self.exit_code === 0) ?
'bg-bgprocess-success' : (self.exit_code == 1) ?
'bg-bgprocess-failed' : ''
),
$logs = container.find('.bg-process-watcher'), $logs = container.find('.bg-process-watcher'),
$header = container.find('.bg-process-details'), $header = container.find('.bg-process-details'),
$footer = container.find('.bg-process-footer'), $footer = container.find('.bg-process-footer'),
@@ -394,9 +396,14 @@ define('misc.bgprocess', [
// Enable/Disable stop process button // Enable/Disable stop process button
if (isNaN(parseInt(self.exit_code))) { if (isNaN(parseInt(self.exit_code))) {
$btn_stop_process.removeClass('disabled'); $btn_stop_process.attr('disabled', false);
} else { } 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. // On Click event to stop the process.
@@ -483,6 +490,8 @@ define('misc.bgprocess', [
stop_process: function() { stop_process: function() {
var self = this; var self = this;
// Set the state to terminated.
self.state = 3;
$.ajax({ $.ajax({
type: 'PUT', type: 'PUT',
timeout: 30000, timeout: 30000,

View File

@@ -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) exit_code = db.Column(db.Integer(), nullable=True)
acknowledge = db.Column(db.String(), nullable=True) acknowledge = db.Column(db.String(), nullable=True)
utility_pid = db.Column(db.Integer, nullable=False) utility_pid = db.Column(db.Integer, nullable=False)
process_state = db.Column(db.Integer, nullable=False)
class Keys(db.Model): class Keys(db.Model):

View File

@@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator
from pickle import dumps, loads from pickle import dumps, loads
if sys.version_info < (3, 3): if sys.version_info < (3, 3):
from mock import patch from mock import patch, MagicMock
else: else:
from unittest.mock import patch from unittest.mock import patch, MagicMock
class BatchProcessTest(BaseTestGenerator): class BatchProcessTest(BaseTestGenerator):
@@ -141,6 +141,7 @@ class BatchProcessTest(BaseTestGenerator):
)) ))
db_mock.session.add.side_effect = db_session_add_mock db_mock.session.add.side_effect = db_session_add_mock
db_mock.session.commit = MagicMock(return_value=True)
get_server_details_mock.return_value = \ get_server_details_mock.return_value = \
self.class_params['name'],\ self.class_params['name'],\
@@ -165,12 +166,29 @@ class BatchProcessTest(BaseTestGenerator):
self.assertTrue(db_mock.session.add.called) self.assertTrue(db_mock.session.add.called)
# Check start method # Check start method
self._check_start(popen_mock, p) self._check_start(popen_mock, p, backup_obj)
# Check list method # Check list method
self._check_list(p, backup_obj) 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'] cmd_test = self.class_params['cmd']
assert_true = self.assertTrue assert_true = self.assertTrue
@@ -202,6 +220,7 @@ class BatchProcessTest(BaseTestGenerator):
self.arguments = " ".join(args) self.arguments = " ".join(args)
self.command = cmd self.command = cmd
self.acknowledge = None self.acknowledge = None
self.process_state = 0
process_mock.query.filter_by.return_value = [ process_mock.query.filter_by.return_value = [
TestMockProcess(backup_obj, TestMockProcess(backup_obj,

View File

@@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator
from pickle import dumps, loads from pickle import dumps, loads
if sys.version_info < (3, 3): if sys.version_info < (3, 3):
from mock import patch from mock import patch, MagicMock
else: else:
from unittest.mock import patch from unittest.mock import patch, MagicMock
class BatchProcessTest(BaseTestGenerator): class BatchProcessTest(BaseTestGenerator):
@@ -89,6 +89,7 @@ class BatchProcessTest(BaseTestGenerator):
mock_result.first.return_value = mock_obj mock_result.first.return_value = mock_obj
db_mock.session.add.side_effect = db_session_add_mock db_mock.session.add.side_effect = db_session_add_mock
db_mock.session.commit = MagicMock(return_value=True)
maintenance_obj = Message( maintenance_obj = Message(
self.class_params['sid'], self.class_params['sid'],
@@ -106,12 +107,30 @@ class BatchProcessTest(BaseTestGenerator):
self.assertTrue(db_mock.session.add.called) self.assertTrue(db_mock.session.add.called)
# Check start method # Check start method
self._check_start(popen_mock, p) self._check_start(popen_mock, p, maintenance_obj)
# Check list method # Check list method
self._check_list(p, maintenance_obj) 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'] cmd_test = self.class_params['cmd']
assert_true = self.assertTrue assert_true = self.assertTrue
@@ -143,6 +162,7 @@ class BatchProcessTest(BaseTestGenerator):
self.arguments = " ".join(args) self.arguments = " ".join(args)
self.command = cmd self.command = cmd
self.acknowledge = None self.acknowledge = None
self.process_state = 0
process_mock.query.filter_by.return_value = [ process_mock.query.filter_by.return_value = [
TestMockProcess(maintenance_obj, TestMockProcess(maintenance_obj,

View File

@@ -15,9 +15,9 @@ from pgadmin.utils.route import BaseTestGenerator
from pickle import dumps, loads from pickle import dumps, loads
if sys.version_info < (3, 3): if sys.version_info < (3, 3):
from mock import patch from mock import patch, MagicMock
else: else:
from unittest.mock import patch from unittest.mock import patch, MagicMock
class BatchProcessTest(BaseTestGenerator): class BatchProcessTest(BaseTestGenerator):
@@ -89,6 +89,7 @@ class BatchProcessTest(BaseTestGenerator):
self.class_params['port'] self.class_params['port']
db_mock.session.add.side_effect = db_session_add_mock db_mock.session.add.side_effect = db_session_add_mock
db_mock.session.commit = MagicMock(return_value=True)
restore_obj = RestoreMessage( restore_obj = RestoreMessage(
self.class_params['sid'], self.class_params['sid'],
@@ -106,12 +107,30 @@ class BatchProcessTest(BaseTestGenerator):
self.assertTrue(db_mock.session.add.called) self.assertTrue(db_mock.session.add.called)
# Check start method # Check start method
self._check_start(popen_mock, p) self._check_start(popen_mock, p, restore_obj)
# Check list method # Check list method
self._check_list(p, restore_obj) 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'] cmd_test = self.class_params['cmd']
assert_true = self.assertTrue assert_true = self.assertTrue
@@ -143,6 +162,7 @@ class BatchProcessTest(BaseTestGenerator):
self.arguments = " ".join(args) self.arguments = " ".join(args)
self.command = cmd self.command = cmd
self.acknowledge = None self.acknowledge = None
self.process_state = 0
process_mock.query.filter_by.return_value = [ process_mock.query.filter_by.return_value = [
TestMockProcess(restore_obj, TestMockProcess(restore_obj,