iotagent-node-lib
Version:
IoT Agent library to interface with NGSI Context Broker
316 lines (283 loc) • 11.4 kB
JavaScript
/*
* Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
*
* This file is part of fiware-iotagent-lib
*
* fiware-iotagent-lib is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* fiware-iotagent-lib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with fiware-iotagent-lib.
* If not, see http://www.gnu.org/licenses/.
*
* For those usages not covered by the GNU Affero General Public License
* please contact with::daniel.moranjimenez@telefonica.com
*/
const async = require('async');
const iotManagerService = require('./../common/iotManagerService');
const intoTrans = require('../common/domain').intoTrans;
const apply = async.apply;
const deviceService = require('../devices/deviceService');
const logger = require('logops');
const config = require('../../commonConfig');
const errors = require('../../errors');
const context = {
op: 'IoTAgentNGSI.DeviceGroupService'
};
/**
* Validate the Device Group: check mandatory params, and its check for duplicates with existing groups. This last
* check may depend on the configuration mode.
*
*
* @param {Object} group Validate the device group
*/
function validateGroup(group, callback) {
const validations = [];
logger.debug(context, 'validateGroup %j', group);
function generateDuplicateHandler(innerCb) {
return function (error, foundGroup) {
logger.debug(context, 'generateDuplicateHander error %j and foundGroup %j', error, foundGroup);
if (!error || (foundGroup && foundGroup.count > 0)) {
innerCb(new errors.DuplicateGroup(group));
} else {
innerCb();
}
};
}
function checkApiKeyAndResource(innerCb) {
config.getGroupRegistry().getSilently(group.resource, group.apikey, generateDuplicateHandler(innerCb));
}
function checkMandatoryParams(innerCb) {
if (!group.service) {
innerCb(new errors.MissingConfigParams(['service']));
return;
}
if (!group.subservice) {
innerCb(new errors.MissingConfigParams(['subservice']));
return;
}
if (!group.type) {
innerCb(new errors.MissingConfigParams(['type']));
return;
}
innerCb();
}
validations.push(checkApiKeyAndResource);
validations.push(checkMandatoryParams);
async.series(validations, callback);
}
/**
* Store a set of groups into the registry. The set should contain a services parameter with an array of groups to add;
* each group is added individually.
*
* @param {Object} groupSet Set of device groups to add to the registry.
*/
function createGroup(groupSet, callback) {
const insertions = [];
const insertedGroups = [];
logger.debug(context, 'Creating new set of %d services', groupSet.services.length);
for (let i = 0; i < groupSet.services.length; i++) {
insertions.push(async.apply(validateGroup, groupSet.services[i]));
insertions.push(async.apply(config.getGroupRegistry().create, groupSet.services[i]));
insertedGroups.push(groupSet.services[i]);
}
insertions.push(iotManagerService.register);
async.series(insertions, callback);
}
/**
* List all the groups created in the IoT Agent. This function can be called in two forms: with a single callback
* parameter, or with four parameters (with optional null in the unwanted options).
*
* @param {Number} service Service for wich all the configurations want to be retrieved.
* @param {Number} limit Maximum number of entries to return.
* @param {Number} offset Number of entries to skip for pagination.
*/
function listGroups(service, limit, offset, callback) {
if (!callback) {
callback = service;
}
config.getGroupRegistry().list(service, limit, offset, callback);
}
function checkServiceIdentity(service, subservice, deviceGroup, callback) {
if (deviceGroup.service === service.toLowerCase() && deviceGroup.subservice === subservice) {
callback(null, deviceGroup);
} else {
callback(new errors.MismatchedService(service, subservice));
}
}
/**
* Generates a handler for group management functions that, if the operation went out correctly, calls the
* IoT Manager registration for group information update.
*
* @return {Function} A handler for the group related functions.
*/
function handleWithIotaRegistration(callback) {
/* eslint-disable-next-line no-unused-vars */
return function (error, objectResult) {
if (error) {
callback(error);
} else {
iotManagerService.register(callback);
}
};
}
/**
* Remove the device group defined by the given service and subservice names from the group registry.
*
* @param {String} service Group service name.
* @param {String} subservice Group subservice name.
* @param {String} resource Resource where the devices will arrive.
* @param {String} apikey Api keys the devices will declare.
*/
function remove(service, subservice, resource, apikey, device, callback) {
function extractId(deviceGroup, callback) {
callback(null, deviceGroup._id);
}
function unregisterDevice(device, cb) {
deviceService.unregister(device.id, device.apikey, service, subservice, function (error) {
if (error) {
cb(error);
}
});
}
function deleteDevices(device, service, subservice, id, callback) {
if (device) {
deviceService.listDevices(service, subservice, function (error, devices) {
if (error) {
callback(error);
} else if (devices && devices.count > 0) {
async.map(devices.devices, unregisterDevice, function (error) {
if (error) {
callback(error);
}
});
}
});
}
callback(null, id);
}
async.waterfall(
[
apply(config.getGroupRegistry().get, resource, apikey),
apply(checkServiceIdentity, service, subservice),
extractId,
apply(deleteDevices, device, service, subservice),
config.getGroupRegistry().remove
],
handleWithIotaRegistration(callback)
);
}
/**
* Update the device group defined by resource and API Key with the values in the new body. The new body does not
* override the old one as a whole: just the attributes present in the new body are changed.
*
* @param {String} service Group service name.
* @param {String} subservice Group subservice name.
* @param {String} resource Resource where the devices will arrive.
* @param {String} apikey Api keys the devices will declare.
* @param {Object} body New body containing the attributes to change.
*/
function update(service, subservice, resource, apikey, body, callback) {
function extractId(deviceGroup, callback) {
callback(null, deviceGroup._id, body);
}
async.waterfall(
[
apply(config.getGroupRegistry().get, resource, apikey),
apply(checkServiceIdentity, service, subservice),
extractId,
config.getGroupRegistry().update
],
handleWithIotaRegistration(callback)
);
}
/**
* Find a device group based on its service and subservice.
*
* @param {String} service Group service name.
* @param {String} subservice Group subservice name.
* @param {String} type Group type (optional).
*/
function find(service, subservice, type, callback) {
if (type) {
config.getGroupRegistry().findType(service, subservice, type, callback);
} else {
config.getGroupRegistry().find(service, subservice, callback);
}
}
/**
* Get the device group identified by the given (resource, apikey) pair.
*
* @param {String} resource Resource where the devices will arrive.
* @param {String} apikey Api keys the devices will declare.
*/
function getGroup(resource, apikey, callback) {
config.getGroupRegistry().get(resource, apikey, callback);
}
/**
* Get the device group identified by the given (resource, apikey) pair,
* allowing not found it (will be created later)
*
* @param {String} resource Resource where the devices will arrive.
* @param {String} apikey Api keys the devices will declare.
*/
function getGroupSilently(resource, apikey, callback) {
config.getGroupRegistry().getSilently(resource, apikey, callback);
}
/**
* Get the API Key for the selected service if there is any, or the default API Key if a specific one does not exist.
*
* @param {String} service Name of the service whose API Key we are retrieving.
* @param {String} subservice Name of the subservice whose API Key we are retrieving.
* @param {String} type Type of the device.
*/
function getEffectiveApiKey(service, subservice, type, callback) {
logger.debug(
context,
'Getting effective API Key for service [%s], subservice [%s] and type [%s]',
service,
subservice,
type
);
function handleFindGroup(error, group) {
if (group) {
logger.debug(context, 'Using found group: %j', group);
callback(null, group.apikey);
} else if (
config.getConfig().types &&
config.getConfig().types[type] &&
config.getConfig().types[type].apikey
) {
logger.debug(context, 'Using API Key for type [%s]: %s', type, config.getConfig().types[type].apikey);
callback(null, config.getConfig().types[type].apikey);
} else if (config.getConfig().defaultKey) {
logger.debug(context, 'Using default API Key: %s', config.getConfig().defaultKey);
callback(null, config.getConfig().defaultKey);
} else {
logger.error(
context,
'Could not find any APIKey information for device in service %s subservice %s and type %s',
service,
subservice,
type
);
callback(new errors.GroupNotFound(service, subservice, type));
}
}
find(service, subservice, type, handleFindGroup);
}
exports.create = intoTrans(context, createGroup);
exports.list = intoTrans(context, listGroups);
exports.find = intoTrans(context, find);
exports.get = intoTrans(context, getGroup);
exports.getSilently = intoTrans(context, getGroupSilently);
exports.update = intoTrans(context, update);
exports.remove = intoTrans(context, remove);
exports.getEffectiveApiKey = intoTrans(context, getEffectiveApiKey);