2016-05-18 22:50:06 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
angular.module('ffffng')
|
2016-05-24 14:42:25 +02:00
|
|
|
.service('MonitoringService', function (
|
|
|
|
_,
|
|
|
|
async,
|
|
|
|
config,
|
2016-06-26 19:09:12 +02:00
|
|
|
deepExtend,
|
2016-05-24 14:42:25 +02:00
|
|
|
Database,
|
2016-06-26 19:09:12 +02:00
|
|
|
DatabaseUtil,
|
2016-05-24 14:42:25 +02:00
|
|
|
ErrorTypes,
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger,
|
2016-05-24 14:42:25 +02:00
|
|
|
moment,
|
2016-05-24 23:32:04 +02:00
|
|
|
MailService,
|
2016-05-24 14:42:25 +02:00
|
|
|
NodeService,
|
|
|
|
request,
|
|
|
|
Strings,
|
2016-05-24 23:32:04 +02:00
|
|
|
UrlBuilder,
|
2016-05-24 14:42:25 +02:00
|
|
|
Validator,
|
2016-06-11 15:31:57 +02:00
|
|
|
Constraints,
|
|
|
|
Resources
|
2016-05-24 14:42:25 +02:00
|
|
|
) {
|
2016-06-26 19:27:09 +02:00
|
|
|
var MONITORING_STATE_MACS_CHUNK_SIZE = 100;
|
2016-05-24 23:32:04 +02:00
|
|
|
var MONITORING_MAILS_DB_BATCH_SIZE = 50;
|
2016-07-24 13:36:56 +02:00
|
|
|
/**
|
|
|
|
* Defines the intervals emails are sent if a node is offline
|
|
|
|
*/
|
2016-05-24 23:32:04 +02:00
|
|
|
var MONITORING_OFFLINE_MAILS_SCHEDULE = {
|
|
|
|
1: { amount: 3, unit: 'hours' },
|
|
|
|
2: { amount: 1, unit: 'days' },
|
|
|
|
3: { amount: 7, unit: 'days' }
|
|
|
|
};
|
2016-07-29 23:09:43 +02:00
|
|
|
var DELETE_OFFLINE_NODES_AFTER_DURATION = {
|
|
|
|
amount: 100,
|
|
|
|
unit: 'days'
|
|
|
|
};
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
var previousImportTimestamp = null;
|
|
|
|
|
|
|
|
function insertNodeInformation(nodeData, node, callback) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Node is new in monitoring, creating data: %s', nodeData.mac);
|
|
|
|
|
|
|
|
return Database.run(
|
|
|
|
'INSERT INTO node_state ' +
|
2016-06-26 19:09:12 +02:00
|
|
|
'(hostname, mac, monitoring_state, state, last_seen, import_timestamp, last_status_mail_sent, last_status_mail_type) ' +
|
2016-06-26 19:38:11 +02:00
|
|
|
'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
2016-05-24 23:32:04 +02:00
|
|
|
[
|
2016-06-26 19:09:12 +02:00
|
|
|
node.hostname,
|
2016-05-24 23:32:04 +02:00
|
|
|
node.mac,
|
2016-06-26 19:09:12 +02:00
|
|
|
node.monitoringState,
|
2016-05-24 23:32:04 +02:00
|
|
|
nodeData.state,
|
|
|
|
nodeData.lastSeen.unix(),
|
|
|
|
nodeData.importTimestamp.unix(),
|
|
|
|
null, // new node so we haven't send a mail yet
|
|
|
|
null // new node so we haven't send a mail yet
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateNodeInformation(nodeData, node, row, callback) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Node is known in monitoring: %s', nodeData.mac);
|
|
|
|
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint -W106
|
2016-05-24 23:32:04 +02:00
|
|
|
if (!moment(row.import_timestamp).isBefore(nodeData.importTimestamp)) {
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint +W106
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
2016-05-24 23:32:04 +02:00
|
|
|
.debug('No new data for node, skipping: %s', nodeData.mac);
|
|
|
|
return callback();
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('New data for node, updating: %s', nodeData.mac);
|
|
|
|
|
|
|
|
return Database.run(
|
|
|
|
'UPDATE node_state ' +
|
2016-06-26 19:09:12 +02:00
|
|
|
'SET hostname = ?, monitoring_state = ?, state = ?, last_seen = ?, import_timestamp = ?, modified_at = ?' +
|
2016-05-24 23:32:04 +02:00
|
|
|
'WHERE id = ? AND mac = ?',
|
|
|
|
[
|
2016-06-26 19:09:12 +02:00
|
|
|
node.hostname,
|
|
|
|
node.monitoringState,
|
|
|
|
nodeData.state,
|
|
|
|
nodeData.lastSeen.unix(),
|
|
|
|
nodeData.importTimestamp.unix(),
|
|
|
|
moment().unix(),
|
2016-05-24 23:32:04 +02:00
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
row.id,
|
|
|
|
node.mac
|
|
|
|
],
|
2016-05-24 23:32:04 +02:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function storeNodeInformation(nodeData, node, callback) {
|
2016-06-26 19:09:12 +02:00
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Storing status for node: %s', nodeData.mac);
|
2016-05-24 23:32:04 +02:00
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-07-29 21:49:03 +02:00
|
|
|
var nodeDataForStoring;
|
|
|
|
if (nodeData === 'missing') {
|
|
|
|
nodeDataForStoring = {
|
|
|
|
mac: node.mac,
|
|
|
|
state: 'OFFLINE',
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint -W106
|
2016-07-29 21:49:03 +02:00
|
|
|
lastSeen: _.isUndefined(row) ? moment() : moment.unix(row.last_seen),
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint +W106
|
2016-07-29 21:49:03 +02:00
|
|
|
importTimestamp: moment()
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
nodeDataForStoring = nodeData;
|
|
|
|
}
|
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
if (_.isUndefined(row)) {
|
2016-07-29 21:49:03 +02:00
|
|
|
return insertNodeInformation(nodeDataForStoring, node, callback);
|
2016-06-26 19:09:12 +02:00
|
|
|
} else {
|
2016-07-29 21:49:03 +02:00
|
|
|
return updateNodeInformation(nodeDataForStoring, node, row, callback);
|
2016-06-26 19:09:12 +02:00
|
|
|
}
|
|
|
|
});
|
2016-05-24 23:32:04 +02:00
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
var isValidMac = Validator.forConstraint(Constraints.node.mac);
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
function parseNodesJson(body, callback) {
|
2016-06-26 12:58:40 +02:00
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Parsing nodes.json...');
|
2016-05-24 16:40:57 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
function parseTimestamp(timestamp) {
|
2017-05-06 17:31:34 +02:00
|
|
|
if (!_.isString(timestamp)) {
|
2016-05-24 23:32:04 +02:00
|
|
|
return moment.invalid();
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
2016-05-24 23:32:04 +02:00
|
|
|
return moment.utc(timestamp);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
var data = {};
|
|
|
|
try {
|
|
|
|
var json = JSON.parse(body);
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
if (json.version !== 1) {
|
|
|
|
return callback(new Error('Unexpected nodes.json version: ' + json.version));
|
|
|
|
}
|
2016-05-24 16:40:57 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
data.importTimestamp = parseTimestamp(json.timestamp);
|
|
|
|
if (!data.importTimestamp.isValid()) {
|
|
|
|
return callback(new Error('Invalid timestamp: ' + json.timestamp));
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
if (!_.isPlainObject(json.nodes)) {
|
|
|
|
return callback(new Error('Invalid nodes object type: ' + (typeof json.nodes)));
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
data.nodes = _.values(_.map(json.nodes, function (nodeData, nodeId) {
|
|
|
|
if (!_.isPlainObject(nodeData)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected node type: ' + (typeof nodeData)
|
|
|
|
);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
if (!_.isPlainObject(nodeData.nodeinfo)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected nodeinfo type: ' + (typeof nodeData.nodeinfo)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!_.isPlainObject(nodeData.nodeinfo.network)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected nodeinfo.network type: ' +
|
|
|
|
(typeof nodeData.nodeinfo.network)
|
|
|
|
);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
if (!isValidMac(nodeData.nodeinfo.network.mac)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Invalid MAC: ' + nodeData.nodeinfo.network.mac
|
|
|
|
);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
2016-05-24 23:32:04 +02:00
|
|
|
var mac = Strings.normalizeMac(nodeData.nodeinfo.network.mac);
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
if (!_.isPlainObject(nodeData.flags)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected flags type: ' + (typeof nodeData.flags)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!_.isBoolean(nodeData.flags.online)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected flags.online type: ' + (typeof nodeData.flags.online)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
var isOnline = nodeData.flags.online;
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
var lastSeen = parseTimestamp(nodeData.lastseen);
|
|
|
|
if (!lastSeen.isValid()) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Invalid lastseen timestamp: ' + nodeData.lastseen
|
|
|
|
);
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
return {
|
|
|
|
mac: mac,
|
|
|
|
importTimestamp: data.importTimestamp,
|
|
|
|
state: isOnline ? 'ONLINE' : 'OFFLINE',
|
|
|
|
lastSeen: lastSeen
|
|
|
|
};
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
return callback(error);
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
callback(null, data);
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-07-29 21:49:03 +02:00
|
|
|
function updateSkippedNode(id, node, callback) {
|
|
|
|
Database.run(
|
|
|
|
'UPDATE node_state ' +
|
|
|
|
'SET hostname = ?, monitoring_state = ?, modified_at = ?' +
|
|
|
|
'WHERE id = ?',
|
|
|
|
[
|
|
|
|
node ? node.hostname : '', node ? node.monitoringState : '', moment().unix(),
|
|
|
|
id
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
2016-06-29 12:02:46 +02:00
|
|
|
|
2016-07-29 21:49:03 +02:00
|
|
|
function sendMonitoringMailsBatched(name, mailType, findBatchFun, callback) {
|
2016-06-26 12:58:40 +02:00
|
|
|
Logger.tag('monitoring', 'mail-sending').debug('Sending "%s" mails...', name);
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
var sendNextBatch = function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
2016-06-26 12:58:40 +02:00
|
|
|
Logger.tag('monitoring', 'mail-sending').debug('Sending next batch...');
|
2016-05-24 23:32:04 +02:00
|
|
|
|
|
|
|
findBatchFun(function (err, nodeStates) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.isEmpty(nodeStates)) {
|
2016-06-26 12:58:40 +02:00
|
|
|
Logger.tag('monitoring', 'mail-sending').debug('Done sending "%s" mails.', name);
|
2016-05-24 23:32:04 +02:00
|
|
|
return callback(null);
|
|
|
|
}
|
2016-05-24 14:42:25 +02:00
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
async.each(
|
|
|
|
nodeStates,
|
|
|
|
function (nodeState, mailCallback) {
|
|
|
|
var mac = nodeState.mac;
|
|
|
|
Logger.tag('monitoring', 'mail-sending').debug('Loading node data for: %s', mac);
|
2016-06-26 19:09:12 +02:00
|
|
|
NodeService.getNodeDataByMac(mac, function (err, node, nodeSecrets) {
|
2016-05-24 23:32:04 +02:00
|
|
|
if (err) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "' + name + '" mail for node: ' + mac, err);
|
2016-06-29 11:28:39 +02:00
|
|
|
return mailCallback(err);
|
2016-05-24 23:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
2016-06-29 11:28:39 +02:00
|
|
|
.debug(
|
2016-06-29 12:02:46 +02:00
|
|
|
'Node not found. Skipping sending of "' + name + '" mail: ' + mac
|
|
|
|
);
|
|
|
|
return updateSkippedNode(nodeState.id, {}, mailCallback);
|
2016-05-24 23:32:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (node.monitoring && node.monitoringConfirmed) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.info('Sending "%s" mail for: %s', name, mac);
|
|
|
|
MailService.enqueue(
|
|
|
|
config.server.email.from,
|
|
|
|
node.nickname + ' <' + node.email + '>',
|
|
|
|
mailType,
|
|
|
|
{
|
|
|
|
node: node,
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint -W106
|
2016-05-24 23:32:04 +02:00
|
|
|
lastSeen: nodeState.last_seen,
|
2017-05-06 17:31:34 +02:00
|
|
|
// jshint +W106
|
2016-05-24 23:32:04 +02:00
|
|
|
disableUrl: UrlBuilder.monitoringDisableUrl(nodeSecrets)
|
|
|
|
|
|
|
|
},
|
|
|
|
function (err) {
|
|
|
|
if (err) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "' + name + '" mail for node: ' + mac, err);
|
|
|
|
return mailCallback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.debug('Updating node state: ', mac);
|
|
|
|
|
|
|
|
var now = moment().unix();
|
|
|
|
Database.run(
|
|
|
|
'UPDATE node_state ' +
|
2016-06-26 19:09:12 +02:00
|
|
|
'SET hostname = ?, monitoring_state = ?, modified_at = ?, last_status_mail_sent = ?, last_status_mail_type = ?' +
|
2016-05-24 23:32:04 +02:00
|
|
|
'WHERE id = ?',
|
|
|
|
[
|
2016-06-26 19:09:12 +02:00
|
|
|
node.hostname, node.monitoringState, now, now, mailType,
|
2016-05-24 23:32:04 +02:00
|
|
|
nodeState.id
|
|
|
|
],
|
|
|
|
mailCallback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
2016-07-23 22:09:13 +02:00
|
|
|
.debug('Monitoring disabled, skipping "%s" mail for: %s', name, mac);
|
2016-06-29 12:02:46 +02:00
|
|
|
return updateSkippedNode(nodeState.id, {}, mailCallback);
|
2016-05-24 23:32:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
sendNextBatch
|
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
sendNextBatch(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendOnlineAgainMails(startTime, callback) {
|
|
|
|
sendMonitoringMailsBatched(
|
|
|
|
'online again',
|
|
|
|
'monitoring-online-again',
|
|
|
|
function (findBatchCallback) {
|
|
|
|
Database.all(
|
|
|
|
'SELECT * FROM node_state ' +
|
|
|
|
'WHERE modified_at < ? AND state = ? AND last_status_mail_type IN (' +
|
|
|
|
'\'monitoring-offline-1\', \'monitoring-offline-2\', \'monitoring-offline-3\'' +
|
|
|
|
')' +
|
|
|
|
'ORDER BY id ASC LIMIT ?',
|
|
|
|
[
|
|
|
|
startTime.unix(),
|
|
|
|
'ONLINE',
|
|
|
|
|
|
|
|
MONITORING_MAILS_DB_BATCH_SIZE
|
|
|
|
],
|
|
|
|
findBatchCallback
|
|
|
|
);
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-07-24 13:36:56 +02:00
|
|
|
/**
|
|
|
|
* sends one of three mails if a node is offline
|
|
|
|
* @param {moment} startTime the moment the job started
|
|
|
|
* @param {Number} mailNumber which of three mails
|
|
|
|
* @param {Function} callback gets all nodes that are offline
|
|
|
|
*/
|
2016-05-24 23:32:04 +02:00
|
|
|
function sendOfflineMails(startTime, mailNumber, callback) {
|
|
|
|
sendMonitoringMailsBatched(
|
|
|
|
'offline ' + mailNumber,
|
|
|
|
'monitoring-offline-' + mailNumber,
|
|
|
|
function (findBatchCallback) {
|
2016-07-24 13:36:56 +02:00
|
|
|
/**
|
|
|
|
* descriptive string that stores, which was the last mail type, stored in the database as last_status_mail_type
|
|
|
|
*/
|
2016-05-24 23:32:04 +02:00
|
|
|
var previousType =
|
2017-05-06 17:31:34 +02:00
|
|
|
mailNumber === 1 ? 'monitoring-online-again' : ('monitoring-offline-' + (mailNumber - 1));
|
2016-05-24 23:32:04 +02:00
|
|
|
|
|
|
|
// the first time the first offline mail is send, there was no mail before
|
|
|
|
var allowNull = mailNumber === 1 ? ' OR last_status_mail_type IS NULL' : '';
|
|
|
|
|
|
|
|
var schedule = MONITORING_OFFLINE_MAILS_SCHEDULE[mailNumber];
|
2016-06-25 14:33:51 +02:00
|
|
|
var scheduledTimeBefore = moment().subtract(schedule.amount, schedule.unit);
|
2016-05-24 23:32:04 +02:00
|
|
|
|
|
|
|
Database.all(
|
|
|
|
'SELECT * FROM node_state ' +
|
|
|
|
'WHERE modified_at < ? AND state = ? AND (last_status_mail_type = ?' + allowNull + ') AND ' +
|
2016-06-25 20:24:28 +02:00
|
|
|
'last_seen <= ? AND (last_status_mail_sent <= ? OR last_status_mail_sent IS NULL) ' +
|
2016-05-24 23:32:04 +02:00
|
|
|
'ORDER BY id ASC LIMIT ?',
|
|
|
|
[
|
|
|
|
startTime.unix(),
|
|
|
|
'OFFLINE',
|
|
|
|
previousType,
|
2016-06-25 14:33:51 +02:00
|
|
|
scheduledTimeBefore.unix(),
|
|
|
|
scheduledTimeBefore.unix(),
|
2016-05-24 23:32:04 +02:00
|
|
|
|
|
|
|
MONITORING_MAILS_DB_BATCH_SIZE
|
|
|
|
],
|
|
|
|
findBatchCallback
|
|
|
|
);
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-07-29 21:49:03 +02:00
|
|
|
function markMissingNodesAsOffline(nodes, callback) {
|
|
|
|
var knownNodes = {};
|
|
|
|
_.each(nodes, function (node) {
|
|
|
|
knownNodes[Strings.normalizeMac(node.mac)] = 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
NodeService.getAllNodes(function (err, nodes) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
async.eachSeries(
|
|
|
|
nodes,
|
|
|
|
function (node, nodeCallback) {
|
|
|
|
if (knownNodes[Strings.normalizeMac(node.mac)]) {
|
|
|
|
// node is known in nodes.json so it has already been handled
|
|
|
|
return nodeCallback(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
storeNodeInformation('missing', node, nodeCallback);
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
function withUrlsData(urls, callback) {
|
|
|
|
async.map(urls, function (url, urlCallback) {
|
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Retrieving nodes.json: %s', url);
|
|
|
|
request(url, function (err, response, body) {
|
|
|
|
if (err) {
|
|
|
|
return urlCallback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.statusCode !== 200) {
|
|
|
|
return urlCallback(new Error(
|
|
|
|
'Could not download nodes.json from ' + url + ': ' +
|
|
|
|
response.statusCode + ' - ' + response.statusMessage
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
parseNodesJson(body, urlCallback);
|
|
|
|
});
|
|
|
|
}, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieveNodeInformationForUrls(urls, callback) {
|
|
|
|
withUrlsData(urls, function (err, datas) {
|
2017-03-03 22:02:26 +01:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
var maxTimestamp = datas[0].importTimestamp;
|
|
|
|
_.each(datas, function (data) {
|
|
|
|
if (data.importTimestamp.isAfter(maxTimestamp)) {
|
|
|
|
maxTimestamp = data.importTimestamp;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (previousImportTimestamp !== null && !maxTimestamp.isAfter(previousImportTimestamp)) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug(
|
|
|
|
'No new data, skipping. Current timestamp: %s, previous timestamp: %s',
|
|
|
|
maxTimestamp.format(),
|
|
|
|
previousImportTimestamp.format()
|
|
|
|
);
|
|
|
|
return callback();
|
2017-03-03 22:02:26 +01:00
|
|
|
}
|
2017-03-06 20:32:52 +01:00
|
|
|
previousImportTimestamp = maxTimestamp;
|
2017-03-03 22:02:26 +01:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
// We do not parallelize here as the sqlite will start slowing down and blocking with too many
|
|
|
|
// parallel queries. This has resulted in blocking other requests too and thus in a major slowdonw.
|
2017-03-19 16:31:50 +01:00
|
|
|
var allNodes = _.flatMap(datas, function (data) {
|
2017-03-06 20:32:52 +01:00
|
|
|
return data.nodes;
|
|
|
|
});
|
2017-03-19 16:31:50 +01:00
|
|
|
|
|
|
|
// Get rid of duplicates from different nodes.json files. Always use the one with the newest
|
|
|
|
var sortedNodes = _.orderBy(allNodes, [function (node) {
|
|
|
|
return node.lastSeen.unix();
|
|
|
|
}], ['desc']);
|
|
|
|
var uniqueNodes = _.uniqBy(sortedNodes, function (node) {
|
|
|
|
return node.mac;
|
|
|
|
});
|
2017-03-06 20:32:52 +01:00
|
|
|
async.eachSeries(
|
2017-03-19 16:31:50 +01:00
|
|
|
uniqueNodes,
|
2017-03-06 20:32:52 +01:00
|
|
|
function (nodeData, nodeCallback) {
|
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Importing: %s', nodeData.mac);
|
2017-03-03 22:02:26 +01:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
NodeService.getNodeDataByMac(nodeData.mac, function (err, node) {
|
|
|
|
if (err) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.error('Error importing: ' + nodeData.mac, err);
|
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
2017-03-03 22:02:26 +01:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
if (!node) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Unknown node, skipping: %s', nodeData.mac);
|
|
|
|
return nodeCallback(null);
|
|
|
|
}
|
2017-03-03 22:02:26 +01:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
storeNodeInformation(nodeData, node, function (err) {
|
2017-03-03 22:02:26 +01:00
|
|
|
if (err) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
2017-03-06 20:32:52 +01:00
|
|
|
.debug('Could not update / deleting node data: %s', nodeData.mac, err);
|
2017-03-03 22:02:26 +01:00
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Updating / deleting node data done: %s', nodeData.mac);
|
2017-03-03 22:02:26 +01:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
nodeCallback();
|
2017-03-03 22:02:26 +01:00
|
|
|
});
|
2017-03-06 20:32:52 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2017-03-03 22:02:26 +01:00
|
|
|
}
|
2017-03-06 20:32:52 +01:00
|
|
|
|
2017-03-19 16:31:50 +01:00
|
|
|
markMissingNodesAsOffline(uniqueNodes, callback);
|
2017-03-06 20:32:52 +01:00
|
|
|
}
|
|
|
|
);
|
2017-03-03 22:02:26 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
return {
|
2016-06-11 15:31:57 +02:00
|
|
|
getAll: function (restParams, callback) {
|
2016-06-26 13:06:58 +02:00
|
|
|
var sortFields = [
|
|
|
|
'id',
|
2016-06-26 19:09:12 +02:00
|
|
|
'hostname',
|
2016-06-26 13:06:58 +02:00
|
|
|
'mac',
|
2016-06-26 19:09:12 +02:00
|
|
|
'monitoring_state',
|
2016-06-26 13:06:58 +02:00
|
|
|
'state',
|
|
|
|
'last_seen',
|
|
|
|
'import_timestamp',
|
|
|
|
'last_status_mail_type',
|
|
|
|
'last_status_mail_sent',
|
|
|
|
'created_at',
|
|
|
|
'modified_at'
|
|
|
|
];
|
|
|
|
var filterFields = [
|
2016-06-26 19:09:12 +02:00
|
|
|
'hostname',
|
2016-06-26 13:06:58 +02:00
|
|
|
'mac',
|
2016-06-26 19:09:12 +02:00
|
|
|
'monitoring_state',
|
2016-06-26 13:06:58 +02:00
|
|
|
'state',
|
|
|
|
'last_status_mail_type'
|
|
|
|
];
|
|
|
|
|
|
|
|
var where = Resources.whereCondition(restParams, filterFields);
|
|
|
|
|
2016-06-11 15:31:57 +02:00
|
|
|
Database.get(
|
2016-06-26 13:06:58 +02:00
|
|
|
'SELECT count(*) AS total FROM node_state WHERE ' + where.query,
|
|
|
|
_.concat([], where.params),
|
2016-06-11 15:31:57 +02:00
|
|
|
function (err, row) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var total = row.total;
|
|
|
|
|
|
|
|
var filter = Resources.filterClause(
|
|
|
|
restParams,
|
|
|
|
'id',
|
2016-06-26 13:06:58 +02:00
|
|
|
sortFields,
|
|
|
|
filterFields
|
2016-06-11 15:31:57 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
Database.all(
|
|
|
|
'SELECT * FROM node_state WHERE ' + filter.query,
|
|
|
|
_.concat([], filter.params),
|
|
|
|
function (err, rows) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, rows, total);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2016-06-26 19:09:12 +02:00
|
|
|
getByMacs: function (macs, callback) {
|
|
|
|
if (_.isEmpty(macs)) {
|
|
|
|
return callback(null, {});
|
|
|
|
}
|
|
|
|
|
2016-06-26 19:27:09 +02:00
|
|
|
async.map(
|
|
|
|
_.chunk(macs, MONITORING_STATE_MACS_CHUNK_SIZE),
|
|
|
|
function (subMacs, subCallback) {
|
|
|
|
var inCondition = DatabaseUtil.inCondition('mac', subMacs);
|
2016-06-26 19:09:12 +02:00
|
|
|
|
2016-06-26 19:27:09 +02:00
|
|
|
Database.all(
|
|
|
|
'SELECT * FROM node_state WHERE ' + inCondition.query,
|
|
|
|
_.concat([], inCondition.params),
|
|
|
|
subCallback
|
|
|
|
);
|
|
|
|
},
|
|
|
|
function (err, rowsArrays) {
|
2016-06-26 19:09:12 +02:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodeStateByMac = {};
|
2016-06-26 19:27:09 +02:00
|
|
|
_.each(_.flatten(rowsArrays), function (row) {
|
2016-06-26 19:09:12 +02:00
|
|
|
nodeStateByMac[row.mac] = row;
|
|
|
|
});
|
|
|
|
|
2016-06-26 19:27:09 +02:00
|
|
|
return callback(null, nodeStateByMac);
|
2016-06-26 19:09:12 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2016-05-23 22:36:27 +02:00
|
|
|
confirm: function (token, callback) {
|
|
|
|
NodeService.getNodeDataByMonitoringToken(token, function (err, node, nodeSecrets) {
|
2016-05-18 22:50:06 +02:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!node.monitoring || !nodeSecrets.monitoringToken || nodeSecrets.monitoringToken !== token) {
|
|
|
|
return callback({data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.monitoringConfirmed) {
|
|
|
|
return callback(null, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
node.monitoringConfirmed = true;
|
2016-05-18 23:15:43 +02:00
|
|
|
NodeService.internalUpdateNode(node.token, node, nodeSecrets, function (err, token, node) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
callback(null, node);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-05-23 22:36:27 +02:00
|
|
|
disable: function (token, callback) {
|
|
|
|
NodeService.getNodeDataByMonitoringToken(token, function (err, node, nodeSecrets) {
|
2016-05-18 23:15:43 +02:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!node.monitoring || !nodeSecrets.monitoringToken || nodeSecrets.monitoringToken !== token) {
|
|
|
|
return callback({data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
|
|
}
|
|
|
|
|
|
|
|
node.monitoring = false;
|
|
|
|
node.monitoringConfirmed = false;
|
|
|
|
nodeSecrets.monitoringToken = '';
|
|
|
|
|
2016-05-18 22:50:06 +02:00
|
|
|
NodeService.internalUpdateNode(node.token, node, nodeSecrets, function (err, token, node) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
callback(null, node);
|
|
|
|
});
|
|
|
|
});
|
2016-05-24 14:42:25 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
retrieveNodeInformation: function (callback) {
|
2017-03-03 22:02:26 +01:00
|
|
|
var urls = config.server.map.nodesJsonUrl;
|
2017-03-06 20:32:52 +01:00
|
|
|
if (_.isEmpty(urls)) {
|
2017-05-13 11:38:51 +02:00
|
|
|
return callback(
|
|
|
|
new Error('No nodes.json-URLs set. Please adjust config.json: server.map.nodesJsonUrl')
|
|
|
|
);
|
2017-03-06 20:32:52 +01:00
|
|
|
}
|
2017-03-03 22:02:26 +01:00
|
|
|
if (_.isString(urls)) {
|
|
|
|
urls = [urls];
|
|
|
|
}
|
2016-07-29 21:49:03 +02:00
|
|
|
|
2017-03-06 20:32:52 +01:00
|
|
|
retrieveNodeInformationForUrls(urls, callback);
|
2016-05-24 20:39:51 +02:00
|
|
|
},
|
|
|
|
|
2016-05-24 23:32:04 +02:00
|
|
|
sendMonitoringMails: function (callback) {
|
2016-06-26 12:58:40 +02:00
|
|
|
Logger.tag('monitoring', 'mail-sending').debug('Sending monitoring mails...');
|
2016-05-24 23:32:04 +02:00
|
|
|
|
|
|
|
var startTime = moment();
|
|
|
|
|
|
|
|
sendOnlineAgainMails(startTime, function (err) {
|
|
|
|
if (err) {
|
|
|
|
// only logging an continuing with next type
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "online again" mails.', err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sendOfflineMails(startTime, 1, function (err) {
|
|
|
|
if (err) {
|
|
|
|
// only logging an continuing with next type
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "offline 1" mails.', err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sendOfflineMails(startTime, 2, function (err) {
|
|
|
|
if (err) {
|
|
|
|
// only logging an continuing with next type
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "offline 2" mails.', err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sendOfflineMails(startTime, 3, function (err) {
|
|
|
|
if (err) {
|
|
|
|
// only logging an continuing with next type
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'mail-sending')
|
|
|
|
.error('Error sending "offline 3" mails.', err);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-07-29 23:09:43 +02:00
|
|
|
deleteOfflineNodes: function (callback) {
|
2016-05-24 20:39:51 +02:00
|
|
|
Logger
|
2016-07-29 23:09:43 +02:00
|
|
|
.tag('nodes', 'delete-offline')
|
|
|
|
.info(
|
|
|
|
'Deleting offline nodes older than ' +
|
|
|
|
DELETE_OFFLINE_NODES_AFTER_DURATION.amount + ' ' +
|
|
|
|
DELETE_OFFLINE_NODES_AFTER_DURATION.unit
|
|
|
|
);
|
2016-05-24 20:39:51 +02:00
|
|
|
|
2016-07-29 23:09:43 +02:00
|
|
|
Database.all(
|
|
|
|
'SELECT * FROM node_state WHERE state = ? AND last_seen < ?',
|
|
|
|
[
|
|
|
|
'OFFLINE',
|
|
|
|
moment().subtract(
|
|
|
|
DELETE_OFFLINE_NODES_AFTER_DURATION.amount,
|
|
|
|
DELETE_OFFLINE_NODES_AFTER_DURATION.unit
|
|
|
|
).unix()
|
|
|
|
],
|
|
|
|
function (err, rows) {
|
|
|
|
async.eachSeries(
|
|
|
|
rows,
|
|
|
|
function (row, nodeCallback) {
|
|
|
|
var mac = row.mac;
|
|
|
|
Logger.tag('nodes', 'delete-offline').info('Deleting node ' + mac);
|
|
|
|
NodeService.getNodeDataByMac(mac, function (err, node) {
|
|
|
|
if (err) {
|
|
|
|
Logger.tag('nodes', 'delete-offline').error('Error getting node ' + mac, err);
|
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
2016-05-24 20:39:51 +02:00
|
|
|
|
2016-09-09 21:51:27 +02:00
|
|
|
async.seq(
|
|
|
|
function (callback) {
|
|
|
|
if (node) {
|
|
|
|
return NodeService.deleteNode(node.token, callback);
|
|
|
|
}
|
|
|
|
return callback(null);
|
|
|
|
},
|
|
|
|
function (callback) {
|
|
|
|
Database.run(
|
|
|
|
'DELETE FROM node_state WHERE mac = ? AND state = ?',
|
|
|
|
[mac, 'OFFLINE'],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
)(function (err) {
|
2016-07-29 23:09:43 +02:00
|
|
|
if (err) {
|
|
|
|
Logger.tag('nodes', 'delete-offline').error('Error deleting node ' + mac, err);
|
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
|
|
|
|
2016-09-09 21:51:27 +02:00
|
|
|
nodeCallback(null);
|
2016-07-29 23:09:43 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
2016-05-24 20:39:51 +02:00
|
|
|
}
|
|
|
|
);
|
2016-05-18 22:50:06 +02:00
|
|
|
}
|
2017-05-06 17:31:34 +02:00
|
|
|
};
|
2016-05-18 22:50:06 +02:00
|
|
|
});
|