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,
|
|
|
|
Database,
|
|
|
|
ErrorTypes,
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger,
|
2016-05-24 14:42:25 +02:00
|
|
|
moment,
|
|
|
|
NodeService,
|
|
|
|
request,
|
|
|
|
Strings,
|
|
|
|
Validator,
|
|
|
|
Constraints
|
|
|
|
) {
|
|
|
|
var previousImportTimestamp = null;
|
|
|
|
|
|
|
|
function insertNodeInformation(nodeData, node, callback) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Node is new in monitoring, creating data:', nodeData.mac);
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
return Database.run(
|
|
|
|
'INSERT INTO node_state ' +
|
|
|
|
'(mac, state, last_seen, import_timestamp, last_status_mail_send) ' +
|
|
|
|
'VALUES (?, ?, ?, ?, ?)',
|
|
|
|
[
|
|
|
|
node.mac,
|
|
|
|
nodeData.state,
|
|
|
|
nodeData.lastSeen.unix(),
|
|
|
|
nodeData.importTimestamp.unix(),
|
|
|
|
null // new node so we haven't send a mail yet
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateNodeInformation(nodeData, node, row, callback) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Node is known in monitoring:', nodeData.mac);
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
if (!moment(row.import_timestamp).isBefore(nodeData.importTimestamp)) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('No new data for node, skipping:', nodeData.mac);
|
2016-05-24 14:42:25 +02:00
|
|
|
return callback();
|
|
|
|
}
|
|
|
|
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('New data for node, updating:', nodeData.mac);
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
return Database.run(
|
|
|
|
'UPDATE node_state ' +
|
2016-05-24 20:29:28 +02:00
|
|
|
'SET state = ?, last_seen = ?, import_timestamp = ?, modified_at = ?' +
|
|
|
|
'WHERE id = ? AND mac = ?',
|
2016-05-24 14:42:25 +02:00
|
|
|
[
|
2016-05-24 20:29:28 +02:00
|
|
|
nodeData.state, nodeData.lastSeen.unix(), nodeData.importTimestamp.unix(), moment().unix(),
|
|
|
|
row.id, node.mac
|
2016-05-24 14:42:25 +02:00
|
|
|
],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteNodeInformation(nodeData, node, callback) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Node is not being monitored, deleting monitoring data:', nodeData.mac);
|
2016-05-24 14:42:25 +02:00
|
|
|
return Database.run(
|
|
|
|
'DELETE FROM node_state WHERE mac = ? AND import_timestamp < ?',
|
|
|
|
[node.mac, nodeData.importTimestamp.unix()],
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function storeNodeInformation(nodeData, node, callback) {
|
|
|
|
if (node.monitoring && node.monitoringConfirmed) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Node is being monitored:', nodeData.mac);
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
return Database.get('SELECT * FROM node_state WHERE mac = ?', [node.mac], function (err, row) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.isUndefined(row)) {
|
|
|
|
return insertNodeInformation(nodeData, node, callback);
|
|
|
|
} else {
|
|
|
|
return updateNodeInformation(nodeData, node, row, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2016-05-24 20:29:28 +02:00
|
|
|
return deleteNodeInformation(nodeData, node, callback);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var isValidMac = Validator.forConstraint(Constraints.node.mac);
|
|
|
|
|
|
|
|
function parseNodesJson(body, callback) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger.tag('monitoring', 'information-retrieval').info('Parsing nodes.json...');
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
function parseTimestamp(timestamp) {
|
|
|
|
if (!_.isString(json.timestamp)) {
|
|
|
|
return moment.invalid();
|
|
|
|
}
|
2016-05-24 20:29:28 +02:00
|
|
|
return moment.utc(timestamp);
|
2016-05-24 14:42:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var data = {};
|
|
|
|
try {
|
|
|
|
var json = JSON.parse(body);
|
|
|
|
|
|
|
|
if (json.version !== 1) {
|
|
|
|
return callback(new Error('Unexpected nodes.json version: ' + json.version));
|
|
|
|
}
|
|
|
|
|
|
|
|
data.importTimestamp = parseTimestamp(json.timestamp);
|
|
|
|
if (!data.importTimestamp.isValid()) {
|
|
|
|
return callback(new Error('Invalid timestamp: ' + json.timestamp));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_.isPlainObject(json.nodes)) {
|
|
|
|
return callback(new Error('Invalid nodes object type: ' + (typeof json.nodes)));
|
|
|
|
}
|
|
|
|
|
|
|
|
data.nodes = _.values(_.map(json.nodes, function (nodeData, nodeId) {
|
|
|
|
if (!_.isPlainObject(nodeData)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Unexpected node type: ' + (typeof nodeData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isValidMac(nodeData.nodeinfo.network.mac)) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Invalid MAC: ' + nodeData.nodeinfo.network.mac
|
|
|
|
);
|
|
|
|
}
|
|
|
|
var mac = Strings.normalizeMac(nodeData.nodeinfo.network.mac);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
var lastSeen = parseTimestamp(nodeData.lastseen);
|
|
|
|
if (!lastSeen.isValid()) {
|
|
|
|
throw new Error(
|
|
|
|
'Node ' + nodeId + ': Invalid lastseen timestamp: ' + nodeData.lastseen
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
mac: mac,
|
|
|
|
importTimestamp: data.importTimestamp,
|
|
|
|
state: isOnline ? 'ONLINE' : 'OFFLINE',
|
|
|
|
lastSeen: lastSeen
|
|
|
|
};
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
return callback(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
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) {
|
2016-05-24 16:40:57 +02:00
|
|
|
var url = config.server.map.nodesJsonUrl;
|
|
|
|
Logger.tag('monitoring', 'information-retrieval').info('Retrieving nodes.json:', url);
|
|
|
|
request(url, function (err, response, body) {
|
2016-05-24 14:42:25 +02:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2016-05-24 19:14:09 +02:00
|
|
|
if (response.statusCode !== 200) {
|
|
|
|
return callback(new Error(
|
|
|
|
'Could not download nodes.json from ' + url + ': ' +
|
|
|
|
response.statusCode + ' - ' + response.statusMessage
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
parseNodesJson(body, function (err, data) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (previousImportTimestamp !== null && !data.importTimestamp.isAfter(previousImportTimestamp)) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.info(
|
|
|
|
'No new data, skipping.',
|
|
|
|
'Current timestamp:',
|
|
|
|
data.importTimestamp.format(),
|
|
|
|
'Previous timestamp:',
|
|
|
|
previousImportTimestamp.format()
|
|
|
|
);
|
2016-05-24 14:42:25 +02:00
|
|
|
return callback();
|
|
|
|
}
|
|
|
|
previousImportTimestamp = data.importTimestamp;
|
|
|
|
|
|
|
|
async.each(
|
|
|
|
data.nodes,
|
|
|
|
function (nodeData, nodeCallback) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger.tag('monitoring', 'information-retrieval').debug('Importing:', nodeData.mac);
|
|
|
|
|
2016-05-24 14:42:25 +02:00
|
|
|
NodeService.findNodeDataByMac(nodeData.mac, function (err, node) {
|
|
|
|
if (err) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.error('Error importing:', nodeData.mac, err);
|
2016-05-24 14:42:25 +02:00
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!node) {
|
2016-05-24 16:40:57 +02:00
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Unknown node, skipping:', nodeData.mac);
|
2016-05-24 14:42:25 +02:00
|
|
|
return nodeCallback(null);
|
|
|
|
}
|
|
|
|
|
2016-05-24 16:40:57 +02:00
|
|
|
storeNodeInformation(nodeData, node, function (err) {
|
|
|
|
if (err) {
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
2016-05-24 20:29:28 +02:00
|
|
|
.debug('Could not update / deleting node data: ', nodeData.mac, err);
|
2016-05-24 16:40:57 +02:00
|
|
|
return nodeCallback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger
|
|
|
|
.tag('monitoring', 'information-retrieval')
|
|
|
|
.debug('Updating / deleting node data done:', nodeData.mac);
|
|
|
|
|
|
|
|
nodeCallback();
|
|
|
|
});
|
2016-05-24 14:42:25 +02:00
|
|
|
});
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2016-05-18 22:50:06 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|