mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
33
web/migrations/versions/b5b87fdfcb30_.py
Normal file
33
web/migrations/versions/b5b87fdfcb30_.py
Normal 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
|
@@ -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(
|
||||||
|
@@ -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,
|
||||||
|
@@ -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):
|
||||||
|
@@ -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,
|
||||||
|
@@ -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,
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user