openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
388 lines (315 loc) • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findAndProcessAQueuedTask = findAndProcessAQueuedTask;
exports.start = start;
exports.stop = stop;
exports.isRunning = isRunning;
var _winston = require('winston');
var _winston2 = _interopRequireDefault(_winston);
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _net = require('net');
var _net2 = _interopRequireDefault(_net);
var _tasks = require('./model/tasks');
var _channels = require('./model/channels');
var _transactions = require('./model/transactions');
var _rerunUpdateTransactionTask = require('./middleware/rerunUpdateTransactionTask');
var rerunMiddleware = _interopRequireWildcard(_rerunUpdateTransactionTask);
var _config = require('./config');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.rerun = _config.config.get('rerun');
let live = false;
let activeTasks = 0;
// TODO : This needs to be converted to an event emitter or an observable
async function findAndProcessAQueuedTask() {
let task;
try {
task = await _tasks.TaskModel.findOneAndUpdate({ status: 'Queued' }, { status: 'Processing' }, { new: true });
if (task != null) {
activeTasks++;
await processNextTaskRound(task);
activeTasks--;
}
} catch (err) {
if (task == null) {
_winston2.default.error(`An error occurred while looking for rerun tasks: ${err}`);
} else {
_winston2.default.error(`An error occurred while processing rerun task ${task._id}: ${err}`);
}
activeTasks--;
}
}
function rerunTaskProcessor() {
if (live) {
findAndProcessAQueuedTask();
return setTimeout(rerunTaskProcessor, _config.config.rerun.processor.pollPeriodMillis);
}
}
function start(callback) {
live = true;
setTimeout(rerunTaskProcessor, _config.config.rerun.processor.pollPeriodMillis);
_winston2.default.info('Started rerun task processor');
return callback();
}
function stop(callback) {
live = false;
const waitForActiveTasks = function () {
if (activeTasks > 0) {
return setTimeout(waitForActiveTasks, 500);
}
_winston2.default.info('Stopped rerun task processor');
return callback();
};
return waitForActiveTasks();
}
function isRunning() {
return live;
}
async function finalizeTaskRound(task) {
const result = await _tasks.TaskModel.findOne({ _id: task._id }, { status: 1 });
if (result.status === 'Processing' && task.remainingTransactions !== 0) {
task.status = 'Queued';
_winston2.default.info(`Round completed for rerun task #${task._id} - ${task.remainingTransactions} transactions remaining`);
} else if (task.remainingTransactions === 0) {
task.status = 'Completed';
task.completedDate = new Date();
_winston2.default.info(`Round completed for rerun task #${task._id} - Task completed`);
} else {
task.status = result.status;
_winston2.default.info(`Round completed for rerun task #${task._id} - Task has been ${result.status}`);
}
await task.save();
}
/**
* Process a task.
*
* Tasks are processed in rounds:
* Each round consists of processing n transactions where n is between 1 and the task's batchSize,
* depending on how many transactions are left to process.
*
* When a round completes, the task will be marked as 'Queued' if it still has transactions remaining.
* The next available core instance will then pick up the task again for the next round.
*
* This model allows the instance the get updated information regarding the task in between rounds:
* i.e. if the server has been stopped, if the task has been paused, etc.
*/
async function processNextTaskRound(task) {
_winston2.default.debug(`Processing next task round: total transactions = ${task.totalTransactions}, remainingTransactions = ${task.remainingTransactions}`);
const nextI = task.transactions.length - task.remainingTransactions;
const transactions = Array.from(task.transactions.slice(nextI, nextI + task.batchSize));
const promises = transactions.map(transaction => {
return new Promise(resolve => {
rerunTransaction(transaction.tid, task._id, (err, response) => {
if (err) {
transaction.tstatus = 'Failed';
transaction.error = err;
_winston2.default.error(`An error occurred while rerunning transaction ${transaction.tid} for task ${task._id}: ${err}`);
} else if ((response != null ? response.status : undefined) === 'Failed') {
transaction.tstatus = 'Failed';
transaction.error = response.message;
_winston2.default.error(`An error occurred while rerunning transaction ${transaction.tid} for task ${task._id}: ${err}`);
} else {
transaction.tstatus = 'Completed';
}
task.remainingTransactions--;
return resolve();
});
transaction.tstatus = 'Processing';
});
});
await Promise.all(promises);
try {
await task.save();
} catch (err) {
_winston2.default.error(`Failed to save current task while processing round: taskID=${task._id}, err=${err}`, err);
}
return finalizeTaskRound(task);
}
function rerunTransaction(transactionID, taskID, callback) {
rerunGetTransaction(transactionID, (err, transaction) => {
if (err) {
return callback(err);
}
// setup the option object for the HTTP Request
return _channels.ChannelModel.findById(transaction.channelID, (err, channel) => {
if (err) {
return callback(err);
}
_winston2.default.info(`Rerunning ${channel.type} transaction`);
if (channel.type === 'http' || channel.type === 'polling') {
rerunSetHTTPRequestOptions(transaction, taskID, (err, options) => {
if (err) {
return callback(err);
}
// Run the HTTP Request with details supplied in options object
return rerunHttpRequestSend(options, transaction, (err, HTTPResponse) => callback(err, HTTPResponse));
});
}
if (channel.type === 'tcp' || channel.type === 'tls') {
return rerunTcpRequestSend(channel, transaction, (err, TCPResponse) => {
if (err) {
return callback(err);
}
// Update original
const ctx = {
parentID: transaction._id,
transactionId: transactionID,
transactionStatus: TCPResponse.status,
taskID
};
return rerunMiddleware.updateOriginalTransaction(ctx, err => {
if (err) {
return callback(err);
}
return rerunMiddleware.updateTask(ctx, callback);
});
});
}
});
});
}
function rerunGetTransaction(transactionID, callback) {
_transactions.TransactionModel.findById(transactionID, (err, transaction) => {
if (transaction == null) {
return callback(new Error(`Transaction ${transactionID} could not be found`), null);
}
// check if 'canRerun' property is false - reject the rerun
if (!transaction.canRerun) {
err = new Error(`Transaction ${transactionID} cannot be rerun as there isn't enough information about the request`);
return callback(err, null);
}
// send the transactions data in callback
return callback(null, transaction);
});
}
/**
* Construct HTTP options to be sent #
*/
function rerunSetHTTPRequestOptions(transaction, taskID, callback) {
if (transaction == null) {
const err = new Error('An empty Transaction object was supplied. Aborting HTTP options configuration');
return callback(err, null);
}
_winston2.default.info(`Rerun Transaction #${transaction._id} - HTTP Request options being configured`);
const options = {
hostname: _config.config.rerun.host,
port: _config.config.rerun.httpPort,
path: transaction.request.path,
method: transaction.request.method,
headers: transaction.request.headers || {}
};
if (transaction.clientID) {
options.headers.clientID = transaction.clientID;
}
options.headers.parentID = transaction._id;
options.headers.taskID = taskID;
if (transaction.request.querystring) {
options.path += `?${transaction.request.querystring}`;
}
return callback(null, options);
}
/**
* Construct HTTP options to be sent #
*/
/**
* Function for sending HTTP Request #
*/
function rerunHttpRequestSend(options, transaction, callback) {
let err;
if (options == null) {
err = new Error('An empty \'Options\' object was supplied. Aborting HTTP Send Request');
return callback(err, null);
}
if (transaction == null) {
err = new Error('An empty \'Transaction\' object was supplied. Aborting HTTP Send Request');
return callback(err, null);
}
const response = {
body: '',
transaction: {}
};
_winston2.default.info(`Rerun Transaction #${transaction._id} - HTTP Request is being sent...`);
const req = _http2.default.request(options, res => {
res.on('data', chunk => {
// response data
response.body += chunk;
});
return res.on('end', err => {
if (err) {
response.transaction.status = 'Failed';
} else {
response.transaction.status = 'Completed';
}
response.status = res.statusCode;
response.message = res.statusMessage;
response.headers = res.headers;
response.timestamp = new Date();
_winston2.default.info(`Rerun Transaction #${transaction._id} - HTTP Response has been captured`);
return callback(null, response);
});
});
req.on('error', err => {
// update the status of the transaction that was processed to indicate it failed to process
if (err) {
response.transaction.status = 'Failed';
}
response.status = 500;
response.message = 'Internal Server Error';
response.timestamp = new Date();
return callback(null, response);
});
// write data to request body
if (transaction.request.method === 'POST' || transaction.request.method === 'PUT') {
if (transaction.request.body != null) {
req.write(transaction.request.body);
}
}
return req.end();
}
function rerunTcpRequestSend(channel, transaction, callback) {
const response = {
body: '',
transaction: {}
};
const client = new _net2.default.Socket();
client.connect(channel.tcpPort, channel.tcpHost, () => {
_winston2.default.info(`Rerun Transaction ${transaction._id}: TCP connection established`);
client.end(transaction.request.body);
});
client.on('data', data => {
response.body += data;
});
client.on('end', data => {
response.status = 200;
response.transaction.status = 'Completed';
response.message = '';
response.headers = {};
response.timestamp = new Date();
_winston2.default.info(`Rerun Transaction #${transaction._id} - TCP Response has been captured`);
callback(null, response);
});
return client.on('error', err => {
// update the status of the transaction that was processed to indicate it failed to process
if (err) {
response.transaction.status = 'Failed';
}
response.status = 500;
response.message = 'Internal Server Error';
response.timestamp = new Date();
return callback(null, response);
});
}
/**
* Export these functions when in the "test" environment #
*/
if (process.env.NODE_ENV === 'test') {
exports.rerunGetTransaction = rerunGetTransaction;
exports.rerunSetHTTPRequestOptions = rerunSetHTTPRequestOptions;
exports.rerunHttpRequestSend = rerunHttpRequestSend;
exports.rerunTcpRequestSend = rerunTcpRequestSend;
exports.findAndProcessAQueuedTask = findAndProcessAQueuedTask;
}
//# sourceMappingURL=tasks.js.map