From 2ed21b297f62a69b4390bbb4595e29a7638f797c Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Feb 2015 18:21:11 +0100 Subject: [PATCH 01/78] ADD Prepare next release --- CHANGES_NEXT_RELEASE | 6 ------ package.json | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e4c6a9ffe..e69de29bb 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,6 +0,0 @@ -- Complete the Provisioning API with CRUD Operations. -- Change signature of unregister() funciton to remove unneeded type. -- Support XML in Context Provider updates and queries. -- Fix multiple errors found during preparation of CPBR8 Workshop. -- Improve the documentation to add: Configuration, Security, and IoT Library testing sections. -- Fix package.json information to prepare the package for publishing. diff --git a/package.json b/package.json index ae02a1e75..c8a9de2a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "iotagent-node-lib", "description": "IoT Agent library to interface with NGSI Context Broker", - "version": "0.2.0", + "version": "0.2.0-next", "homepage": "https://github.com/telefonicaid/iotagent-node-lib", "author": { "name": "Daniel Moran", From ad7c30ef406be180f92c154cc5f5b2a4565d2751 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Feb 2015 18:24:30 +0100 Subject: [PATCH 02/78] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index de1b65865..e615a7b3b 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ Given the aforementioned requirements, there are some aspects of the implementat ## Usage ### Library usage #### General review -Note: as it is not yet published in npm repositories, this module has to be currently used as a github dependency in the package.json. To do so, add the following dependency to your package.json file, indicating the branch you want to use: +In order to use the library, add the following dependency to your package.json file: ``` -"iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib/tarball/develop" +"iotagent-node-lib": "*" ``` In order to use this library, first you must require it: ``` From 6fca693e99ce1a0a6152b8471502de159670eefe Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Thu, 12 Feb 2015 15:18:28 +0100 Subject: [PATCH 03/78] ADD Keywords to the package.json --- README.md | 2 +- package.json | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de1b65865..b6ee9ca25 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# fiware-iotagent-lib +# FIWARE IoT Agent Framework ## Index diff --git a/package.json b/package.json index c8a9de2a2..5d036c994 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,13 @@ "description": "IoT Agent library to interface with NGSI Context Broker", "version": "0.2.0-next", "homepage": "https://github.com/telefonicaid/iotagent-node-lib", + "keywords": [ + "fiware", + "iotagent", + "ngsi", + "context broker", + "browser" + ], "author": { "name": "Daniel Moran", "email": "daniel.moranjimenez@telefonica.com" From 5369460a7410945a48fddd7ede0d628b1dd8c3be Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Thu, 12 Feb 2015 15:18:51 +0100 Subject: [PATCH 04/78] FIX Change next release --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e69de29bb..4e61dcea6 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -0,0 +1 @@ +- ADD Keywords to the package.json. \ No newline at end of file From b0153be79677c103dac3e36eb6f80be43996ad77 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Feb 2015 17:30:46 +0100 Subject: [PATCH 05/78] ADD API Routes and checks --- lib/errors.js | 10 ++++++++++ lib/fiware-iotagent-lib.js | 2 ++ lib/model/dbConn.js | 1 + lib/services/northboundServer.js | 9 ++++++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/errors.js b/lib/errors.js index 871cb0618..92f470d38 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -95,5 +95,15 @@ module.exports = { TemplateLoadingError: function(error) { this.name = 'TEMPLATE_LOADING_ERROR'; this.message = 'Some of the XML Templates could not be found or loaded: ' + error.toString(); + }, + MissingHeaders: function(msg) { + this.name = 'MISSING_HEADERS'; + this.message = 'Some headers were missing from the request: ' + msg; + this.code = 400; + }, + WrongSyntax: function(msg) { + this.name = 'WRONG_SYNTAX'; + this.message = 'Wrong syntax in request: ' + msg; + this.code = 400; } }; diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index 07f14dc80..b21cffe45 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -25,6 +25,7 @@ var async = require('async'), ngsi = require('./services/ngsiService'), + groupConfig = require('./services/groupService'), security = require('./services/securityService'), contextServer = require('./services/northboundServer'), logger = require('fiware-node-logger'), @@ -68,6 +69,7 @@ function activate(newConfig, callback) { async.series([ async.apply(registry.init, newConfig), async.apply(ngsi.init, registry, newConfig), + async.apply(groupConfig.init, registry, newConfig), async.apply(security.init, newConfig), async.apply(contextServer.start, newConfig) ], callback); diff --git a/lib/model/dbConn.js b/lib/model/dbConn.js index 11309833f..4e199e561 100644 --- a/lib/model/dbConn.js +++ b/lib/model/dbConn.js @@ -32,6 +32,7 @@ var mongoose = require('mongoose'), function loadModels() { require('./Device').load(defaultDb); + require('./Group').load(defaultDb); } /** diff --git a/lib/services/northboundServer.js b/lib/services/northboundServer.js index f99ca9e34..b77e9b6f3 100644 --- a/lib/services/northboundServer.js +++ b/lib/services/northboundServer.js @@ -27,6 +27,7 @@ var http = require('http'), northboundServer, contextServer = require('./contextServer'), deviceProvisioning = require('./deviceProvisioningServer'), + groupProvisioning = require('./deviceGroupAdministrationServer'), logger = require('fiware-node-logger'), utils = require('./restUtils'), context = { @@ -63,7 +64,8 @@ function traceRequest(req, res, next) { } function start(config, callback) { - var baseRoot = '/'; + var baseRoot = '/', + agentName = ''; northboundServer = { server: null, @@ -87,9 +89,14 @@ function start(config, callback) { baseRoot = config.server.baseRoot; } + if (config.server.name) { + agentName = config.server.name; + } + northboundServer.app.use(baseRoot, northboundServer.router); contextServer.loadContextRoutes(northboundServer.router); deviceProvisioning.loadContextRoutes(northboundServer.router); + groupProvisioning.loadContextRoutes(northboundServer.router, agentName); northboundServer.app.use(handleError); From a89122a16d59a67d3c79a63cbe9122564135ec24 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Feb 2015 17:41:09 +0100 Subject: [PATCH 06/78] ADD Missing files --- lib/model/Group.js | 47 +++ .../deviceGroupAdministrationServer.js | 100 ++++++ lib/services/groupService.js | 63 ++++ lib/templates/deviceGroup.json | 52 ++++ test/unit/device-group-api-test.js | 285 ++++++++++++++++++ 5 files changed, 547 insertions(+) create mode 100644 lib/model/Group.js create mode 100644 lib/services/deviceGroupAdministrationServer.js create mode 100644 lib/services/groupService.js create mode 100644 lib/templates/deviceGroup.json create mode 100644 test/unit/device-group-api-test.js diff --git a/lib/model/Group.js b/lib/model/Group.js new file mode 100644 index 000000000..6abed55a6 --- /dev/null +++ b/lib/model/Group.js @@ -0,0 +1,47 @@ +/* + * Copyright 2014 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + */ +'use strict'; + +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +var Group = new Schema({ + url: String, + apikey: String, + type: String, + service: String, + subservice: String, + trust: String, + cbHost: String, + timezone: String, + commands: Array, + lazy: Array, + active: Array +}); + +function load(db) { + module.exports.model = db.model('Group', Group); + module.exports.internalSchema = Group; +} + +module.exports.load = load; diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js new file mode 100644 index 000000000..117874cf9 --- /dev/null +++ b/lib/services/deviceGroupAdministrationServer.js @@ -0,0 +1,100 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + */ +'use strict'; + +var logger = require('fiware-node-logger'), + errors = require('../errors'), + _ = require('underscore'), + revalidator = require('revalidator'), + templateGroup = require('../templates/deviceGroup.json'), + mandatoryHeaders = [ + 'fiware-service', + 'fiware-servicepath' + ]; + +/** + * Checks for the pressence of all the mandatory headers, returning a BAD_REQUEST error if any one is not found. + * + * @param {Object} req Incoming request. + * @param {Object} res Outgoing response. + * @param {Function} next Invokes the next middleware in the chain. + */ +function checkHeaders(req, res, next) { + var headerKeys = _.keys(req.headers), + missing = []; + + for (var i = 0; i < mandatoryHeaders.length; i++) { + if (headerKeys.indexOf(mandatoryHeaders[i]) < 0) { + missing.push(mandatoryHeaders[i]); + } + } + + if (missing.length !== 0) { + next(new errors.MissingHeaders(JSON.stringify(missing))); + } else { + next(); + } +} + +function checkBody(template) { + return function bodyMiddleware(req, res, next) { + var errorList = revalidator.validate(req.body, template); + + if (errorList.valid) { + next(); + } else { + logger.debug(context, 'Errors found validating request: %j', errorList); + next(new errors.WrongSyntax('Errors found validating request.')); + } + }; +} + +function handleCreateDeviceGroup(req, res, next) { + res.status(200).send({}); +} + +function handleListDeviceGroups(req, res, next) { + res.status(200).send({}); +} + +function handleModifyDeviceGroups(req, res, next) { + res.status(200).send({}); +} + +function handleDeleteDeviceGroups(req, res, next) { + res.status(200).send({}); +} + +/** + * Load the routes related to device provisioning in the Express App. + * + * @param {Object} router Express request router object. + */ +function loadContextRoutes(router, name) { + router.post('/iot/agents/' + name, checkHeaders, checkBody(templateGroup), handleCreateDeviceGroup); + router.get('/iot/agents/' + name, handleListDeviceGroups); + router.put('/iot/agents/' + name, checkHeaders, handleModifyDeviceGroups); + router.delete('/iot/agents/' + name, checkHeaders, handleDeleteDeviceGroups); +} + +exports.loadContextRoutes = loadContextRoutes; \ No newline at end of file diff --git a/lib/services/groupService.js b/lib/services/groupService.js new file mode 100644 index 000000000..25779b65b --- /dev/null +++ b/lib/services/groupService.js @@ -0,0 +1,63 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + */ + +'use strict'; + +var request = require('request'), + async = require('async'), + apply = async.apply, + errors = require('../errors'), + logger = require('fiware-node-logger'), + _ = require('underscore'), + context = { + op: 'IoTAgentNGSI.DeviceGroupService' + }, + registry, + config; + + +function createGroup(groupObj, callback) { + callback(); +} + +function listGroups(callback) { + callback(); +} + +/** + * Initializes the device Group service. The initialization requires a configuration object and a reference to a device + * registry. + * + * @param {Object} newRegistry Reference to a device registry, where the devices information will be stored. + * @param {Object} newConfig Configuration object. + */ +function init(newRegistry, newConfig, callback) { + registry = newRegistry; + config = newConfig; + + callback(null); +} + +exports.init = init; +exports.create = createGroup; +exports.list = listGroups; \ No newline at end of file diff --git a/lib/templates/deviceGroup.json b/lib/templates/deviceGroup.json new file mode 100644 index 000000000..21d5178f8 --- /dev/null +++ b/lib/templates/deviceGroup.json @@ -0,0 +1,52 @@ +{ + "title": "Service", + "description": "A service", + "additionalProperties": false, + "type": "object", + "properties": { + "services": { + "type": "array", + "id": "services", + "required": true, + "items": { + "type": "object", + "properties": { + "entity_type": { + "description": "default entity_type, if a device has not got entity_type uses this", + "type": "string" + }, + "apikey": { + "description": "apikey", + "type": "string", + "required": true + }, + "token": { + "description": "token", + "type": "string" + }, + "cbroker": { + "description": "uri for the context broker", + "type": "string" + }, + "resource": { + "description": "uri for the iotagent", + "type": "string", + "required": true + }, + "lazy": { + "description": "list of lazy attributes of the devices", + "type": "array" + }, + "attributes": { + "description": "list of active attributes of the devices", + "type": "array" + }, + "commands": { + "description": "list of commands of the devices", + "type": "array" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js new file mode 100644 index 000000000..262f36066 --- /dev/null +++ b/test/unit/device-group-api-test.js @@ -0,0 +1,285 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + */ +'use strict'; + +var iotAgentLib = require('../../'), + request = require('request'), + should = require('should'), + iotAgentConfig = { + logLevel: 'FATAL', + contextBroker: { + host: '10.11.128.16', + port: '1026' + }, + server: { + name: 'testAgent', + port: 4041, + baseRoot: '/' + }, + types: {}, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S' + }, + optionsCreation = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'POST', + json: { + services: [ + { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732', + type: 'Light', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://unexistentHost:1026', + commands: [ + { + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }, + optionsDelete = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'DELETE', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/*' + } + }, + optionsUpdate = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'PUT', + json: { + services: [ + { + type: 'Light', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://unexistentHost:1026', + commands: [ + { + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }; + +describe.only('Device Group Configuration API', function() { + + beforeEach(function(done) { + iotAgentLib.activate(iotAgentConfig, done); + }); + + afterEach(function(done) { + iotAgentLib.deactivate(done); + }); + describe('When a new device group creation request arrives', function() { + it('should return a 200 OK', function(done) { + request(optionsCreation, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + it('should store it in the DB'); + it('should store the service information from the headers into the DB'); + it('should add the device group to the statically configured ones'); + }); + describe('When a creation request arrives without the fiware-service header', function() { + beforeEach(function() { + delete optionsCreation.headers['fiware-service']; + }); + + afterEach(function() { + optionsCreation.headers['fiware-service'] = 'TestService'; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsCreation, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + describe('When a creation request arrives without the fiware-servicepath header', function() { + beforeEach(function() { + delete optionsCreation.headers['fiware-servicepath']; + }); + + afterEach(function() { + optionsCreation.headers['fiware-servicepath'] = '/testingPath'; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsCreation, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + describe('When a device group with a missing mandatory attribute in the payload arrives', function() { + beforeEach(function() { + delete optionsCreation.json.services[0].resource; + }); + + afterEach(function() { + optionsCreation.json.services[0].resource = '/deviceTest'; + }); + + it('should fail with a 400 WRONG_SYNTAX error', function(done) { + request(optionsCreation, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('WRONG_SYNTAX'); + done(); + }); + }); + }); + describe('When a device group removal request arrives', function() { + it('should return a 200 OK', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + it('should remove it from the database'); + it('should remove it from the configuration'); + }); + + describe('When a device group removal request arrives without the mandatory headers', function() { + beforeEach(function() { + delete optionsDelete.headers['fiware-servicepath']; + }); + + afterEach(function() { + optionsDelete.headers['fiware-servicepath'] = '/testingPath'; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + + describe('When a device group update request arrives', function() { + it('should return a 200 OK', function(done) { + request(optionsUpdate, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + it('should update the values in the database'); + it('should update the values in the configuration'); + }); + + describe('When a device group update request arrives without the mandatory headers', function() { + beforeEach(function() { + delete optionsUpdate.headers['fiware-servicepath']; + }); + + afterEach(function() { + optionsUpdate.headers['fiware-servicepath'] = '/testingPath'; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsUpdate, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + + describe('When a device group listing request arrives', function() { + var options = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'GET', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/*' + } + }; + + it('should return a 200 OK', function(done) { + request(options, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + it('should return all the configured device groups from the database'); + }); +}); \ No newline at end of file From 11740725425eef7cd1624f7f66e7ef1ba477e9c1 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Feb 2015 18:21:48 +0100 Subject: [PATCH 07/78] ADD Create device group (In-Memory registry) --- lib/fiware-iotagent-lib.js | 8 ++- .../deviceGroupAdministrationServer.js | 25 ++++++- lib/services/groupRegistryMemory.js | 72 +++++++++++++++++++ lib/services/groupRegistryMongoDB.js | 0 lib/services/groupService.js | 12 +++- test/unit/device-group-api-test.js | 51 +++++++++---- 6 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 lib/services/groupRegistryMemory.js create mode 100644 lib/services/groupRegistryMongoDB.js diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index b21cffe45..523b62655 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -41,7 +41,8 @@ var async = require('async'), * @param {Object} newConfig Configuration of the Context Server */ function activate(newConfig, callback) { - var registry; + var registry, + groupRegistry; config = newConfig; @@ -57,11 +58,12 @@ function activate(newConfig, callback) { logger.info(context, 'MongoDB Device registry selected for NGSI Library'); registry = require('./services/deviceRegistryMongoDB'); - + groupRegistry = require('./services/groupRegistryMongoDB'); } else { logger.info(context, 'Falling back to Transient Memory registry for NGSI Library'); registry = require('./services/deviceRegistryMemory'); + groupRegistry = require('./services/groupRegistryMemory'); } exports.clearAll = registry.clear; @@ -69,7 +71,7 @@ function activate(newConfig, callback) { async.series([ async.apply(registry.init, newConfig), async.apply(ngsi.init, registry, newConfig), - async.apply(groupConfig.init, registry, newConfig), + async.apply(groupConfig.init, groupRegistry, newConfig), async.apply(security.init, newConfig), async.apply(contextServer.start, newConfig) ], callback); diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 117874cf9..fa6ed931b 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -24,6 +24,7 @@ var logger = require('fiware-node-logger'), errors = require('../errors'), + groupService = require('./groupService'), _ = require('underscore'), revalidator = require('revalidator'), templateGroup = require('../templates/deviceGroup.json'), @@ -70,11 +71,31 @@ function checkBody(template) { } function handleCreateDeviceGroup(req, res, next) { - res.status(200).send({}); + for (var i = 0; i < req.body.services.length; i++) { + req.body.services[i].service = req.headers['fiware-service']; + req.body.services[i].subservice = req.headers['fiware-servicepath']; + } + + groupService.create(req.body, function(error) { + if (error) { + next(error); + } else { + res.status(200).send({}); + } + }); } function handleListDeviceGroups(req, res, next) { - res.status(200).send({}); + groupService.list(function(error, groupList) { + if (error) { + next(error); + } else { + res.status(200).send({ + count: groupList.length, + services: groupList + }); + } + }); } function handleModifyDeviceGroups(req, res, next) { diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js new file mode 100644 index 000000000..ce4e96baf --- /dev/null +++ b/lib/services/groupRegistryMemory.js @@ -0,0 +1,72 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + */ +'use strict'; + +var registeredGroups = {}, + logger = require('fiware-node-logger'), + errors = require('../errors'), + _ = require('underscore'), + context = { + op: 'IoTAgentNGSI.InMemoryGroupRegister' + }, + groupIds = 1; + +function createGroup(group, callback) { + var storeGroup = _.clone(group); + + storeGroup.id = groupIds++; + + registeredGroups[storeGroup.id] = storeGroup; + registeredGroups[storeGroup.id].creationDate = Date.now(); + + logger.debug(context, 'Storing device group for service [%s] and subservice [%s]', + storeGroup.id, storeGroup.service, storeGroup.subservice); + + callback(null); +} + +function listGroups(callback) { + var result = []; + + for (var i in registeredGroups) { + if (registeredGroups.hasOwnProperty(i)) { + result.push(registeredGroups[i]); + } + } + callback(null, result); +} + +function init(newConfig, callback) { + callback(null); +} + +function clear(callback) { + registeredGroups = {}; + + callback(); +} + +exports.create = createGroup; +exports.list = listGroups; +exports.init = init; +exports.clear = clear; \ No newline at end of file diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js new file mode 100644 index 000000000..e69de29bb diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 25779b65b..e045694b4 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -36,12 +36,18 @@ var request = require('request'), config; -function createGroup(groupObj, callback) { - callback(); +function createGroup(groupSet, callback) { + var insertions = []; + + for (var i = 0; i < groupSet.services.length; i++) { + insertions.push(async.apply(registry.create, groupSet.services[i])); + } + + async.series(insertions, callback); } function listGroups(callback) { - callback(); + registry.list(callback); } /** diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 262f36066..b8e9f30f4 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -23,6 +23,7 @@ 'use strict'; var iotAgentLib = require('../../'), + groupRegistryMemory = require('../../lib/services/groupRegistryMemory'), request = require('request'), should = require('should'), iotAgentConfig = { @@ -123,16 +124,29 @@ var iotAgentLib = require('../../'), 'fiware-service': 'TestService', 'fiware-servicepath': '/testingPath' } + }, + optionsList = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'GET', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/*' + } }; describe.only('Device Group Configuration API', function() { beforeEach(function(done) { - iotAgentLib.activate(iotAgentConfig, done); + iotAgentLib.activate(iotAgentConfig, function() { + groupRegistryMemory.clear(done); + }); }); afterEach(function(done) { - iotAgentLib.deactivate(done); + iotAgentLib.deactivate(function() { + groupRegistryMemory.clear(done); + }); }); describe('When a new device group creation request arrives', function() { it('should return a 200 OK', function(done) { @@ -142,8 +156,25 @@ describe.only('Device Group Configuration API', function() { done(); }); }); - it('should store it in the DB'); - it('should store the service information from the headers into the DB'); + it('should store it in the DB', function(done) { + request(optionsCreation, function(error, response, body) { + request(optionsList, function(error, response, body) { + body.count.should.equal(1); + body.services[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732'); + done(); + }); + }); + }); + it('should store the service information from the headers into the DB', function(done) { + request(optionsCreation, function(error, response, body) { + request(optionsList, function(error, response, body) { + body.count.should.equal(1); + body.services[0].service.should.equal('TestService'); + body.services[0].subservice.should.equal('/testingPath'); + done(); + }); + }); + }); it('should add the device group to the statically configured ones'); }); describe('When a creation request arrives without the fiware-service header', function() { @@ -263,18 +294,8 @@ describe.only('Device Group Configuration API', function() { }); describe('When a device group listing request arrives', function() { - var options = { - url: 'http://localhost:4041/iot/agents/testAgent', - method: 'GET', - json: {}, - headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/*' - } - }; - it('should return a 200 OK', function(done) { - request(options, function(error, response, body) { + request(optionsList, function(error, response, body) { should.not.exist(error); response.statusCode.should.equal(200); done(); From ba2504b621244ce037e8db42cd77e9cbec17c806 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 07:50:22 +0100 Subject: [PATCH 08/78] ADD Update and delete operations --- lib/errors.js | 10 +++ .../deviceGroupAdministrationServer.js | 16 +++- lib/services/groupRegistryMemory.js | 44 ++++++++++ lib/services/groupService.js | 24 ++++- test/unit/device-group-api-test.js | 87 +++++++++++++------ 5 files changed, 152 insertions(+), 29 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 92f470d38..617896d88 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -105,5 +105,15 @@ module.exports = { this.name = 'WRONG_SYNTAX'; this.message = 'Wrong syntax in request: ' + msg; this.code = 400; + }, + DeviceGroupNotFound: function(service, subservice) { + this.name = 'DEVICE_GROUP_NOT_FOUND'; + if (subservice) { + this.message = 'Couldn\t find device group for service [' + service + '] and subservice [' + subservice + ']'; + } else { + this.message = 'Couldn\t find device group with id [' + service + ']'; + } + this.code = 400; } + }; diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index fa6ed931b..b36569b23 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -99,11 +99,23 @@ function handleListDeviceGroups(req, res, next) { } function handleModifyDeviceGroups(req, res, next) { - res.status(200).send({}); + groupService.update(req.headers['fiware-service'], req.headers['fiware-servicepath'], req.body, function(error) { + if (error) { + next(error); + } else { + res.status(200).send({}); + } + }); } function handleDeleteDeviceGroups(req, res, next) { - res.status(200).send({}); + groupService.remove(req.headers['fiware-service'], req.headers['fiware-servicepath'], function(error) { + if (error) { + next(error); + } else { + res.status(200).send({}); + } + }); } /** diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index ce4e96baf..3e129c813 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -66,7 +66,51 @@ function clear(callback) { callback(); } +function find(service, subservice, callback) { + var result; + + for (var i in registeredGroups) { + if (registeredGroups.hasOwnProperty(i) && + registeredGroups[i].service === service && + registeredGroups[i].subservice === subservice) { + result = registeredGroups[i]; + break; + } + } + + if (result) { + callback(null, result); + } else { + callback(new errors.DeviceGroupNotFound(service, subservice)); + } +} + +function update(id, body, callback) { + var groupToModify = registeredGroups[id]; + + if (groupToModify) { + for (var i in body) { + if (body.hasOwnProperty(i)) { + groupToModify[i] = body[i]; + } + } + + callback(); + } else { + callback(new errors.DeviceGroupNotFound(id)); + } +} + +function remove(id, callback) { + delete registeredGroups[id]; + + callback(null); +} + exports.create = createGroup; exports.list = listGroups; exports.init = init; +exports.find = find; +exports.update = update; +exports.remove = remove; exports.clear = clear; \ No newline at end of file diff --git a/lib/services/groupService.js b/lib/services/groupService.js index e045694b4..fb823fdff 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -50,6 +50,26 @@ function listGroups(callback) { registry.list(callback); } +function remove(service, subservice, callback) { + registry.find(service, subservice, function (error, deviceGroup) { + if (error) { + callback(error); + } else { + registry.remove(deviceGroup.id, callback); + } + }); +} + +function update(service, subservice, body, callback) { + registry.find(service, subservice, function (error, deviceGroup) { + if (error) { + callback(error); + } else { + registry.update(deviceGroup.id, body, callback); + } + }); +} + /** * Initializes the device Group service. The initialization requires a configuration object and a reference to a device * registry. @@ -66,4 +86,6 @@ function init(newRegistry, newConfig, callback) { exports.init = init; exports.create = createGroup; -exports.list = listGroups; \ No newline at end of file +exports.list = listGroups; +exports.update = update; +exports.remove = remove; \ No newline at end of file diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index b8e9f30f4..01fe46216 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -23,6 +23,7 @@ 'use strict'; var iotAgentLib = require('../../'), + async = require('async'), groupRegistryMemory = require('../../lib/services/groupRegistryMemory'), request = require('request'), should = require('should'), @@ -87,36 +88,32 @@ var iotAgentLib = require('../../'), json: {}, headers: { 'fiware-service': 'TestService', - 'fiware-servicepath': '/*' + 'fiware-servicepath': '/testingPath' } }, optionsUpdate = { url: 'http://localhost:4041/iot/agents/testAgent', method: 'PUT', json: { - services: [ + type: 'LampLight', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://anotherUnexistentHost:1026', + commands: [ { - type: 'Light', - trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', - cbHost: 'http://unexistentHost:1026', - commands: [ - { - name: 'wheel1', - type: 'Wheel' - } - ], - lazy: [ - { - name: 'luminescence', - type: 'Lumens' - } - ], - active: [ - { - name: 'status', - type: 'Boolean' - } - ] + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' } ] }, @@ -232,6 +229,10 @@ describe.only('Device Group Configuration API', function() { }); }); describe('When a device group removal request arrives', function() { + beforeEach(function(done) { + request(optionsCreation, done); + }); + it('should return a 200 OK', function(done) { request(optionsDelete, function(error, response, body) { should.not.exist(error); @@ -239,7 +240,14 @@ describe.only('Device Group Configuration API', function() { done(); }); }); - it('should remove it from the database'); + it('should remove it from the database', function(done) { + request(optionsDelete, function(error, response, body) { + request(optionsList, function(error, response, body) { + body.count.should.equal(0); + done(); + }); + }); + }); it('should remove it from the configuration'); }); @@ -263,6 +271,10 @@ describe.only('Device Group Configuration API', function() { }); describe('When a device group update request arrives', function() { + beforeEach(function(done) { + request(optionsCreation, done); + }); + it('should return a 200 OK', function(done) { request(optionsUpdate, function(error, response, body) { should.not.exist(error); @@ -270,7 +282,17 @@ describe.only('Device Group Configuration API', function() { done(); }); }); - it('should update the values in the database'); + + it('should update the values in the database', function(done) { + request(optionsUpdate, function(error, response, body) { + request(optionsList, function(error, response, body) { + body.count.should.equal(1); + body.services[0].type.should.equal('LampLight'); + body.services[0].cbHost.should.equal('http://anotherUnexistentHost:1026'); + done(); + }); + }); + }); it('should update the values in the configuration'); }); @@ -294,6 +316,14 @@ describe.only('Device Group Configuration API', function() { }); describe('When a device group listing request arrives', function() { + beforeEach(function(done) { + async.series([ + async.apply(request, optionsCreation), + async.apply(request, optionsCreation), + async.apply(request, optionsCreation) + ], done); + }); + it('should return a 200 OK', function(done) { request(optionsList, function(error, response, body) { should.not.exist(error); @@ -301,6 +331,11 @@ describe.only('Device Group Configuration API', function() { done(); }); }); - it('should return all the configured device groups from the database'); + it('should return all the configured device groups from the database', function(done) { + request(optionsList, function(error, response, body) { + body.count.should.equal(3); + done(); + }); + }); }); }); \ No newline at end of file From e8536c7264e99d97b87106c7d4d1d7a749403d0a Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 07:53:59 +0100 Subject: [PATCH 09/78] FIX Linter errors --- lib/errors.js | 3 ++- lib/services/deviceGroupAdministrationServer.js | 5 ++++- lib/services/groupRegistryMemory.js | 2 +- lib/services/groupService.js | 14 ++++++-------- test/unit/device-group-api-test.js | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 617896d88..ec4722f24 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -109,7 +109,8 @@ module.exports = { DeviceGroupNotFound: function(service, subservice) { this.name = 'DEVICE_GROUP_NOT_FOUND'; if (subservice) { - this.message = 'Couldn\t find device group for service [' + service + '] and subservice [' + subservice + ']'; + this.message = 'Couldn\t find device group for service [' + service + + '] and subservice [' + subservice + ']'; } else { this.message = 'Couldn\t find device group with id [' + service + ']'; } diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index b36569b23..f59c6076c 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -28,6 +28,9 @@ var logger = require('fiware-node-logger'), _ = require('underscore'), revalidator = require('revalidator'), templateGroup = require('../templates/deviceGroup.json'), + context = { + op: 'IoTAgentNGSI.GroupServer' + }, mandatoryHeaders = [ 'fiware-service', 'fiware-servicepath' @@ -130,4 +133,4 @@ function loadContextRoutes(router, name) { router.delete('/iot/agents/' + name, checkHeaders, handleDeleteDeviceGroups); } -exports.loadContextRoutes = loadContextRoutes; \ No newline at end of file +exports.loadContextRoutes = loadContextRoutes; diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index 3e129c813..c01b90fe4 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -113,4 +113,4 @@ exports.init = init; exports.find = find; exports.update = update; exports.remove = remove; -exports.clear = clear; \ No newline at end of file +exports.clear = clear; diff --git a/lib/services/groupService.js b/lib/services/groupService.js index fb823fdff..85c9d6123 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -23,12 +23,8 @@ 'use strict'; -var request = require('request'), - async = require('async'), - apply = async.apply, - errors = require('../errors'), +var async = require('async'), logger = require('fiware-node-logger'), - _ = require('underscore'), context = { op: 'IoTAgentNGSI.DeviceGroupService' }, @@ -39,6 +35,8 @@ var request = require('request'), function createGroup(groupSet, callback) { var insertions = []; + logger.debug(context, 'Creating new set of %d services', groupSet.length); + for (var i = 0; i < groupSet.services.length; i++) { insertions.push(async.apply(registry.create, groupSet.services[i])); } @@ -51,7 +49,7 @@ function listGroups(callback) { } function remove(service, subservice, callback) { - registry.find(service, subservice, function (error, deviceGroup) { + registry.find(service, subservice, function(error, deviceGroup) { if (error) { callback(error); } else { @@ -61,7 +59,7 @@ function remove(service, subservice, callback) { } function update(service, subservice, body, callback) { - registry.find(service, subservice, function (error, deviceGroup) { + registry.find(service, subservice, function(error, deviceGroup) { if (error) { callback(error); } else { @@ -88,4 +86,4 @@ exports.init = init; exports.create = createGroup; exports.list = listGroups; exports.update = update; -exports.remove = remove; \ No newline at end of file +exports.remove = remove; diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 01fe46216..d0d75bc55 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -338,4 +338,4 @@ describe.only('Device Group Configuration API', function() { }); }); }); -}); \ No newline at end of file +}); From 61f3aca9dd83ad46a67f41ff7952255c58db50ef Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 08:30:11 +0100 Subject: [PATCH 10/78] ADD Configuration management --- lib/services/groupRegistryMemory.js | 5 ++- lib/services/groupService.js | 62 +++++++++++++++++++++-------- test/unit/device-group-api-test.js | 23 ++++++++--- 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index c01b90fe4..1de8dcaca 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -95,16 +95,17 @@ function update(id, body, callback) { } } - callback(); + callback(null, groupToModify); } else { callback(new errors.DeviceGroupNotFound(id)); } } function remove(id, callback) { + var removedObject = registeredGroups[id]; delete registeredGroups[id]; - callback(null); + callback(null, removedObject); } exports.create = createGroup; diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 85c9d6123..64adcb413 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -24,6 +24,7 @@ 'use strict'; var async = require('async'), + apply = async.apply, logger = require('fiware-node-logger'), context = { op: 'IoTAgentNGSI.DeviceGroupService' @@ -33,15 +34,27 @@ var async = require('async'), function createGroup(groupSet, callback) { - var insertions = []; + var insertions = [], + insertedGroups = []; logger.debug(context, 'Creating new set of %d services', groupSet.length); for (var i = 0; i < groupSet.services.length; i++) { insertions.push(async.apply(registry.create, groupSet.services[i])); + insertedGroups.push(groupSet.services[i]); } - async.series(insertions, callback); + async.series(insertions, function (error) { + if (error) { + callback(error); + } else { + for (var j = 0; j < insertedGroups.length; j++) { + config.types[insertedGroups[j].type] = insertedGroups[j]; + } + + callback(); + } + }); } function listGroups(callback) { @@ -49,23 +62,40 @@ function listGroups(callback) { } function remove(service, subservice, callback) { - registry.find(service, subservice, function(error, deviceGroup) { - if (error) { - callback(error); - } else { - registry.remove(deviceGroup.id, callback); - } - }); + function extractId(deviceGroup, callback) { + callback(null, deviceGroup.id); + } + + function removeFromConfig(deviceGroup, callback) { + delete config.types[deviceGroup.type]; + callback(); + } + + async.waterfall([ + apply(registry.find, service, subservice), + extractId, + registry.remove, + removeFromConfig + ], callback); } function update(service, subservice, body, callback) { - registry.find(service, subservice, function(error, deviceGroup) { - if (error) { - callback(error); - } else { - registry.update(deviceGroup.id, body, callback); - } - }); + function extractId(deviceGroup, callback) { + callback(null, deviceGroup.id, body); + } + + function updateConfig(deviceGroup, callback) { + config.types[deviceGroup.type] = deviceGroup; + + callback(); + } + + async.waterfall([ + apply(registry.find, service, subservice), + extractId, + registry.update, + updateConfig + ], callback); } /** diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index d0d75bc55..5618d9ad0 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -95,7 +95,6 @@ var iotAgentLib = require('../../'), url: 'http://localhost:4041/iot/agents/testAgent', method: 'PUT', json: { - type: 'LampLight', trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', cbHost: 'http://anotherUnexistentHost:1026', commands: [ @@ -172,7 +171,12 @@ describe.only('Device Group Configuration API', function() { }); }); }); - it('should add the device group to the statically configured ones'); + it('should add the device group to the statically configured ones', function(done) { + request(optionsCreation, function(error, response, body) { + should.exist(iotAgentConfig.types['Light']); + done(); + }); + }); }); describe('When a creation request arrives without the fiware-service header', function() { beforeEach(function() { @@ -248,7 +252,12 @@ describe.only('Device Group Configuration API', function() { }); }); }); - it('should remove it from the configuration'); + it('should remove it from the configuration', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(iotAgentConfig.types['Light']); + done(); + }); + }); }); describe('When a device group removal request arrives without the mandatory headers', function() { @@ -287,13 +296,17 @@ describe.only('Device Group Configuration API', function() { request(optionsUpdate, function(error, response, body) { request(optionsList, function(error, response, body) { body.count.should.equal(1); - body.services[0].type.should.equal('LampLight'); body.services[0].cbHost.should.equal('http://anotherUnexistentHost:1026'); done(); }); }); }); - it('should update the values in the configuration'); + it('should update the values in the configuration', function(done) { + request(optionsUpdate, function(error, response, body) { + iotAgentConfig.types['Light'].cbHost.should.equal('http://anotherUnexistentHost:1026'); + done(); + }); + }); }); describe('When a device group update request arrives without the mandatory headers', function() { From 849752a028b29bd2ad0776cc86e96b3fcec45aa1 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 10:56:42 +0100 Subject: [PATCH 11/78] ADD Get operation --- .../deviceGroupAdministrationServer.js | 32 ++++++++++++------- lib/services/groupService.js | 5 +++ test/unit/device-group-api-test.js | 31 ++++++++++++++++++ 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index f59c6076c..288fb146d 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -89,16 +89,26 @@ function handleCreateDeviceGroup(req, res, next) { } function handleListDeviceGroups(req, res, next) { - groupService.list(function(error, groupList) { - if (error) { - next(error); - } else { - res.status(200).send({ - count: groupList.length, - services: groupList - }); - } - }); + if (req.headers['fiware-servicepath'] === '/*') { + groupService.list(function(error, groupList) { + if (error) { + next(error); + } else { + res.status(200).send({ + count: groupList.length, + services: groupList + }); + } + }); + } else { + groupService.find(req.headers['fiware-service'], req.headers['fiware-servicepath'], function(error, group) { + if (error) { + next(error); + } else { + res.status(200).send(group); + } + }); + } } function handleModifyDeviceGroups(req, res, next) { @@ -128,7 +138,7 @@ function handleDeleteDeviceGroups(req, res, next) { */ function loadContextRoutes(router, name) { router.post('/iot/agents/' + name, checkHeaders, checkBody(templateGroup), handleCreateDeviceGroup); - router.get('/iot/agents/' + name, handleListDeviceGroups); + router.get('/iot/agents/' + name, checkHeaders, handleListDeviceGroups); router.put('/iot/agents/' + name, checkHeaders, handleModifyDeviceGroups); router.delete('/iot/agents/' + name, checkHeaders, handleDeleteDeviceGroups); } diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 64adcb413..f14cee803 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -98,6 +98,10 @@ function update(service, subservice, body, callback) { ], callback); } +function find(service, subservice, callback) { + registry.find(service, subservice, callback); +} + /** * Initializes the device Group service. The initialization requires a configuration object and a reference to a device * registry. @@ -115,5 +119,6 @@ function init(newRegistry, newConfig, callback) { exports.init = init; exports.create = createGroup; exports.list = listGroups; +exports.find = find; exports.update = update; exports.remove = remove; diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 5618d9ad0..15d5d6349 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -129,6 +129,15 @@ var iotAgentLib = require('../../'), 'fiware-service': 'TestService', 'fiware-servicepath': '/*' } + }, + optionsGet = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'GET', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } }; describe.only('Device Group Configuration API', function() { @@ -351,4 +360,26 @@ describe.only('Device Group Configuration API', function() { }); }); }); + + describe('When a device info request arrives', function() { + beforeEach(function(done) { + async.series([ + async.apply(request, optionsCreation) + ], done); + }); + + it('should return a 200 OK', function(done) { + request(optionsGet, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + it('should return all the configured device groups from the database', function(done) { + request(optionsGet, function(error, response, body) { + body.service.should.equal('TestService'); + done(); + }); + }); + }); }); From bf296855f8a628e1fa7b2741c6fcf02ef9b79999 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 11:00:33 +0100 Subject: [PATCH 12/78] FIX Linter errors --- lib/services/groupService.js | 2 +- test/unit/device-group-api-test.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/services/groupService.js b/lib/services/groupService.js index f14cee803..56e16f805 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -44,7 +44,7 @@ function createGroup(groupSet, callback) { insertedGroups.push(groupSet.services[i]); } - async.series(insertions, function (error) { + async.series(insertions, function(error) { if (error) { callback(error); } else { diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 15d5d6349..0794f9069 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -140,7 +140,7 @@ var iotAgentLib = require('../../'), } }; -describe.only('Device Group Configuration API', function() { +describe('Device Group Configuration API', function() { beforeEach(function(done) { iotAgentLib.activate(iotAgentConfig, function() { @@ -182,6 +182,8 @@ describe.only('Device Group Configuration API', function() { }); it('should add the device group to the statically configured ones', function(done) { request(optionsCreation, function(error, response, body) { + /* jshint sub:true */ + should.exist(iotAgentConfig.types['Light']); done(); }); @@ -263,6 +265,8 @@ describe.only('Device Group Configuration API', function() { }); it('should remove it from the configuration', function(done) { request(optionsDelete, function(error, response, body) { + /* jshint sub:true */ + should.not.exist(iotAgentConfig.types['Light']); done(); }); @@ -312,6 +316,8 @@ describe.only('Device Group Configuration API', function() { }); it('should update the values in the configuration', function(done) { request(optionsUpdate, function(error, response, body) { + /* jshint sub:true */ + iotAgentConfig.types['Light'].cbHost.should.equal('http://anotherUnexistentHost:1026'); done(); }); From 20c646c0b2668327302947fcf4824ade1ecd6002 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 12:31:55 +0100 Subject: [PATCH 13/78] ADD JSDoc --- .../deviceGroupAdministrationServer.js | 29 +++++++++++++++++++ lib/services/groupService.js | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 288fb146d..4c6182262 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -60,6 +60,12 @@ function checkHeaders(req, res, next) { } } +/** + * Generates a middleware that checks the request body against the given revalidator template. + * + * @param {Object} template Loaded JSON Scheam Template. + * @return {Function} Express middleware that checks the validity of the body. + */ function checkBody(template) { return function bodyMiddleware(req, res, next) { var errorList = revalidator.validate(req.body, template); @@ -73,6 +79,13 @@ function checkBody(template) { }; } +/** + * Handle the device group creation requests, adding the header information to the device group body. + * + * @param {Object} req Incoming request. + * @param {Object} res Outgoing response. + * @param {Function} next Invokes the next middleware in the chain. + */ function handleCreateDeviceGroup(req, res, next) { for (var i = 0; i < req.body.services.length; i++) { req.body.services[i].service = req.headers['fiware-service']; @@ -88,6 +101,15 @@ function handleCreateDeviceGroup(req, res, next) { }); } +/** + * Handle GET requests for device groups. Two kind of requests kind arrive: those with the wildcard servicepath ('/*') + * and requests for a specific subservice. The former ones are considered service listings, and an array of all the + * subservices is returned. For the latter, the description of the specific subservice is returned instead. + * + * @param {Object} req Incoming request. + * @param {Object} res Outgoing response. + * @param {Function} next Invokes the next middleware in the chain. + */ function handleListDeviceGroups(req, res, next) { if (req.headers['fiware-servicepath'] === '/*') { groupService.list(function(error, groupList) { @@ -111,6 +133,13 @@ function handleListDeviceGroups(req, res, next) { } } +/** + * Handle a request for modifications of device groups. + * + * @param {Object} req Incoming request. + * @param {Object} res Outgoing response. + * @param {Function} next Invokes the next middleware in the chain. + */ function handleModifyDeviceGroups(req, res, next) { groupService.update(req.headers['fiware-service'], req.headers['fiware-servicepath'], req.body, function(error) { if (error) { diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 56e16f805..e0dc9b423 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -33,6 +33,12 @@ var async = require('async'), config; +/** + * 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) { var insertions = [], insertedGroups = []; @@ -57,10 +63,19 @@ function createGroup(groupSet, callback) { }); } +/** + * List all the groups present in the registry. + */ function listGroups(callback) { registry.list(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. + */ function remove(service, subservice, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id); @@ -79,6 +94,14 @@ function remove(service, subservice, callback) { ], callback); } +/** + * Update the device group defined by a service and subservice 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 {Object} body New body containing the attributes to change. + */ function update(service, subservice, body, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id, body); @@ -98,6 +121,12 @@ function update(service, subservice, body, callback) { ], callback); } +/** + * Find a device group based on its service and subservice. + * + * @param {String} service Group service name. + * @param {String} subservice Group subservice name. + */ function find(service, subservice, callback) { registry.find(service, subservice, callback); } From 8fe631af5bb92c7e022081e28116f944444bd1b2 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 12:44:22 +0100 Subject: [PATCH 14/78] Update README.md --- README.md | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9ac483804..995932f94 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * [IoT Library testing](#librarytesting) * [Configuration](#configuration) * [Device Provisioning API](#provisioningapi) +* [Configuration API](#configurationapi) * [Secured access to the Context Broker](#securedaccess) * [Development Documentation](#development) @@ -364,24 +365,12 @@ These are the parameters that can be configured in the global section: db: 'iotagent' } ``` -* **types**: See **Type Configuration** section below. +* **types**: See **Type Configuration** in the [Configuration API](#configurationapi) section below. * **service**: default service for the IoT Agent. If a device is being registered, and no service information comes with the device data, and no service information is configured for the given type, the default IoT agent service will be used instead. E.g.: 'smartGondor'. * **subservice**: default subservice for the IoT Agent. If a device is being registered, and no subservice information comes with the device data, and no subservice information is configured for the given type, the default IoT agent subservice will be used instead. E.g.: '/gardens'. * **providerUrl**: URL to send in the Context Provider registration requests. Should represent the external IP of the deployed IoT Agent (the IP where the Context Broker will redirect the NGSI requests). E.g.: 'http://192.168.56.1:4041'. * **deviceRegistrationDuration**: duration of the registrations as Context Providers, in [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) standard format. E.g.: 'P1M'. -### Type Configuration -The IoT Agent can be configured to expect certain kinds of devices, with preconfigured sets of attributes, service information, security information and other attributes. The `types` attribute of the configuration is a map, where the key is the type name and the value is an object containing all the type information. Each type can has the following information configured: - -* service: service of the devices of this type. -* subservice: subservice of the devices of this type. -* active: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. -* lazy: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. -* commands: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. -* internalAttributes: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. -* trust: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). -* contextBroker: Context Broker connection information. This options can be used to override the global ones for specific types of devices. - ## Device Provisioning API ### Overview The IoT Agents offer a provisioning API where devices can be preregistered, so all the information about service and subservice mapping, security information and attribute configuration can be specified in a per device way instead of relaying on the type configuration. The following section specified the format of the device payload; this will be the payload accepted by all the write operations and that will be returned by all the read operations. @@ -440,6 +429,31 @@ Returns: * 404 NOT FOUND if the device was not found in the database. * 500 SERVER ERROR if there was any error not contemplated above. +## Configuration API +For some services, there will be no need to provision individual devices, but it will make more sense to provision different device groups, each of one mapped to a different type of entity in the context broker. How the type of entity is assigned to a device will depend on the Southbound technology (e.g.: path, port, APIKey...). Once the device has an assigned type, its configuration values can be extracted from those of the type. + +The IoT Agents provide two means to define those device groups: +* Static **Type Configuration**: configuring the `ngsi.types` property in the `config.js` file. +* Dinamic **Configuration API**: making use of the API URLS in the configuration URI, `/iot/agent/:agentName/services`. + +Both approaches are better described in the sections bellow. + +### Configuration API + + + +### Type Configuration +The IoT Agent can be configured to expect certain kinds of devices, with preconfigured sets of attributes, service information, security information and other attributes. The `types` attribute of the configuration is a map, where the key is the type name and the value is an object containing all the type information. Each type can has the following information configured: + +* service: service of the devices of this type. +* subservice: subservice of the devices of this type. +* active: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. +* lazy: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. +* commands: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. +* internalAttributes: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. +* trust: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). +* contextBroker: Context Broker connection information. This options can be used to override the global ones for specific types of devices. + ## Secured access to the Context Broker For access to instances of the Context Broker secured with a [PEP Proxy](https://github.com/telefonicaid/fiware-orion-pep), an authentication mechanism based in Keystone Trust tokens is provided. A Trust token is a long-term token that can be issued by any user to give another user permissions to impersonate him with a given role in a given project. From a48c85e315fda70bc47c208369943341e81a9894 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 15:31:17 +0100 Subject: [PATCH 15/78] Update README.md --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 995932f94..6dcda03b3 100644 --- a/README.md +++ b/README.md @@ -434,13 +434,105 @@ For some services, there will be no need to provision individual devices, but it The IoT Agents provide two means to define those device groups: * Static **Type Configuration**: configuring the `ngsi.types` property in the `config.js` file. -* Dinamic **Configuration API**: making use of the API URLS in the configuration URI, `/iot/agent/:agentName/services`. +* Dinamic **Configuration API**: making use of the API URLS in the configuration URI, `/iot/agent/:agentName/services`. Please, note that the configuration API manage servers under an URL that requires the `server.name` parameter to be set (the name of the IoT Agent we are using). If no name is configured `default` is taken as the default one. -Both approaches are better described in the sections bellow. +Both approaches provide the same configuration information for the types (and they, in fact, end up in the same configuration collection), but, for the moment, the file and API nomenclatures differ (to be fixed soon, issue #33). + +Both approaches are better described in the sections bellow. ### Configuration API +The following sections show the available operations for the Configuration API. Every operation in the API require the `fiware-service` and `fiware-servicepath` to be defined; the operations are performed in the scope of those headers. For the list case, the special wildcard servicepath can be specified, '/*'. In this case, the operation applies to all the subservices of the service given by the `fiware-service` header. + +#### Device Group Model +Device groups contain the following attributes: +* **service**: service of the devices of this type. +* **subservice**: subservice of the devices of this type. +* **resource**: string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). +* **apikey**: API Key string. +* **type**: name of the type to assign to the group. +* **trust**: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). +* **cbHost**: Context Broker connection information. This options can be used to override the global ones for specific types of devices. +* **lazy**: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. +* **commands**: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. +* **active**: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. +* **internalAttributes**: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. + +#### POST /iot/agent/:agentName/services +Creates a set of device groups for the given service and service path. The service and subservice information will taken from the headers, overwritting any preexisting values. + +Body params: +* services: list of device groups to create. Each one adheres to the Device Group Model. + +E.g.: +``` +{ + services: [ + { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732', + type: 'Light', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://unexistentHost:1026', + commands: [ + { + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' + } + ] + } + ] +} +``` +Returns: +* 200 OK if successful, with no payload. +* 400 MISSING_HEADERS if any of the mandatory headers is not present. +* 400 WRONG_SYNTAX if the body doesn't comply with the schema. +* 500 SERVER ERROR if there was any error not contemplated above. + +#### GET /iot/agent/:agentName/services +Retrieves device groups from the database. If the servicepath header has de wildcard expression, '/*', all the subservices for the service are returned. The specific subservice parameters are returned in any other case. + +Returns: +* 200 OK if successful, returning a device group body. +* 400 MISSING_HEADERS if any of the mandatory headers is not present. +* 500 SERVER ERROR if there was any error not contemplated above. +#### PUT /iot/agent/:agentName/services +Modifies the information for a device group configuration, identified by its service and subservice. Takes a device group body as the payload. The body does not have to be complete: for incomplete bodies, just the existing attributes will be updated + +E.g.: +``` +{ + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://anotherUnexistentHost:1026' + } +``` + +Returns: +* 200 OK if successful, returning the updated body. +* 400 MISSING_HEADERS if any of the mandatory headers is not present. +* 500 SERVER ERROR if there was any error not contemplated above. + +#### DELETE /iot/agent/:agentName/services +Removes a device group configuration from the DB, specified by the service and subservice headers. + +Returns: +* 200 OK if successful. +* 400 MISSING_HEADERS if any of the mandatory headers is not present. +* 500 SERVER ERROR if there was any error not contemplated above. ### Type Configuration The IoT Agent can be configured to expect certain kinds of devices, with preconfigured sets of attributes, service information, security information and other attributes. The `types` attribute of the configuration is a map, where the key is the type name and the value is an object containing all the type information. Each type can has the following information configured: From 33f2bb145bdb512e2d4a32d5235929a000986e93 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 15:32:01 +0100 Subject: [PATCH 16/78] FIX Default agent name --- lib/services/northboundServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/northboundServer.js b/lib/services/northboundServer.js index b77e9b6f3..5a844eee0 100644 --- a/lib/services/northboundServer.js +++ b/lib/services/northboundServer.js @@ -65,7 +65,7 @@ function traceRequest(req, res, next) { function start(config, callback) { var baseRoot = '/', - agentName = ''; + agentName = 'default'; northboundServer = { server: null, From abef615f69f95c7753aafd7a98500cb0be5aafcb Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 18:44:11 +0100 Subject: [PATCH 17/78] ADD MongoDB registry --- lib/model/Group.js | 1 + .../deviceGroupAdministrationServer.js | 8 + lib/services/groupRegistryMongoDB.js | 206 ++++++++++++++ test/unit/mongodb-group-registry-test.js | 267 ++++++++++++++++++ 4 files changed, 482 insertions(+) create mode 100644 test/unit/mongodb-group-registry-test.js diff --git a/lib/model/Group.js b/lib/model/Group.js index 6abed55a6..512ffa17d 100644 --- a/lib/model/Group.js +++ b/lib/model/Group.js @@ -26,6 +26,7 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema; var Group = new Schema({ + id: String, url: String, apikey: String, type: String, diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 4c6182262..e41122388 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -150,6 +150,14 @@ function handleModifyDeviceGroups(req, res, next) { }); } +/** + * Handle a request for the removal of a device group. + * + * @param {Object} req Incoming request. + * @param {Object} res Outgoing response. + * @param {Function} next Invokes the next middleware in the chain. + */ + function handleDeleteDeviceGroups(req, res, next) { groupService.remove(req.headers['fiware-service'], req.headers['fiware-servicepath'], function(error) { if (error) { diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index e69de29bb..74c00445f 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -0,0 +1,206 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + */ +'use strict'; + +var logger = require('fiware-node-logger'), + dbService = require('../model/dbConn'), + errors = require('../errors'), + Group = require('../model/Group'), + _ = require('underscore'), + DEFAULT_DB_NAME = 'iotagent', + context = { + op: 'IoTAgentNGSI.MongoDBGroupRegister' + }; + +/** + * Generates a handler for the save device group operations. The handler will take the customary error and the saved + * device group as the parameters (and pass the serialized DAO as the callback value). + * + * @return {Function} The generated handler. + */ +function saveGroupHandler(callback) { + return function saveHandler(error, groupDAO) { + if (error) { + logger.debug(context, 'Error storing device group information: %s', error); + + callback(new errors.InternalDbError(error)); + } else { + callback(null, groupDAO.toObject()); + } + }; +} + +function createGroup(group, callback) { + var groupObj = new Group.model(), + attributeList = [ + 'id', + 'url', + 'apikey', + 'type', + 'service', + 'subservice', + 'trust', + 'cbHost', + 'timezone', + 'commands', + 'lazy', + 'active' + ]; + + for (var i = 0; i < attributeList.length; i++) { + groupObj[attributeList[i]] = group[attributeList[i]]; + } + + logger.debug(context, 'Storing device group with id [%s] and type [%s]', groupObj.id, groupObj.type); + + groupObj.save(saveGroupHandler(callback)); +} + +function listGroups(callback) { + var condition = {}, + query; + + query = Group.model.find(condition).sort(); + + query.exec(callback); +} + +function getById(id, callback) { + var query; + + logger.debug(context, 'Looking for device group with id [%s].', id); + + query = Group.model.findOne({id: id}); + query.select({__v: 0}); + + query.exec(function handleGet(error, data) { + if (error) { + logger.debug(context, 'Internal MongoDB Error getting device: %s', error); + + callback(new errors.InternalDbError(error)); + } else if (data) { + callback(null, data); + } else { + logger.debug(context, 'Device group [%s] not found.', id); + + callback(new errors.DeviceGroupNotFound(id)); + } + }); +} + +function find(service, subservice, callback) { + var query; + + logger.debug(context, 'Looking for entity with service name [%s] and subservice [%s].', service, subservice); + + query = Group.model.findOne({ + service: service, + subservice: subservice + }); + + query.select({__v: 0}); + + query.exec(function handleGet(error, data) { + if (error) { + logger.debug(context, 'Internal MongoDB Error getting device: %s', error); + + callback(new errors.InternalDbError(error)); + } else if (data) { + callback(null, data); + } else { + logger.debug(context, 'Device group [%s] not found.', id); + + callback(new errors.DeviceGroupNotFound(id)); + } + }); +} + +function update(id, body, callback) { + getById(id, function (error, group) { + if (error) { + callback(error); + } else { + var attributes = [ + 'url', + 'apikey', + 'type', + 'service', + 'subservice', + 'trust', + 'cbHost', + 'timezone', + 'commands', + 'lazy', + 'active' + ]; + + for (var i = 0; i < attributes.length; i++) { + group[attributes[i]] = body[attributes[i]]; + } + + group.save(saveGroupHandler(callback)); + } + }); +} + +function remove(id, callback) { + logger.debug(context, 'Removing device group with id [%s]', id); + + getById(id, function(error, deviceGroup) { + if (error) { + callback(error); + } else { + Group.model.remove({ id: id }, function(error, number, test) { + if (error) { + logger.debug(context, 'Internal MongoDB Error getting device: %s', error); + + callback(new errors.InternalDbError(error)); + } else if (number === 1) { + logger.debug(context, 'Entity [%s] successfully removed.', id); + + callback(null, deviceGroup); + } else { + logger.debug(context, 'Entity [%s] not found for removal.', id); + + callback(new errors.DeviceGroupNotFound(id)); + } + }); + } + }); +} + +function init(newConfig, callback) { + callback(null); +} + +function clear(callback) { + dbService.dropDatabase(callback); +} + +exports.create = createGroup; +exports.list = listGroups; +exports.init = init; +exports.find = find; +exports.update = update; +exports.remove = remove; +exports.clear = clear; diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js new file mode 100644 index 000000000..ad12995fc --- /dev/null +++ b/test/unit/mongodb-group-registry-test.js @@ -0,0 +1,267 @@ +/* + * 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, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + */ +'use strict'; + +var iotAgentLib = require('../../'), + async = require('async'), + groupRegistryMemory = require('../../lib/services/groupRegistryMemory'), + request = require('request'), + should = require('should'), + iotAgentConfig = { + logLevel: 'FATAL', + contextBroker: { + host: '10.11.128.16', + port: '1026' + }, + server: { + name: 'testAgent', + port: 4041, + baseRoot: '/' + }, + types: {}, + deviceRegistry: { + type: 'mongodb', + host: 'localhost', + port: '27017', + db: 'iotagent' + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S' + }, + mongo = require('mongodb').MongoClient, + mongoUtils = require('./mongoDBUtils'), + optionsCreation = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'POST', + json: { + services: [ + { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732', + type: 'Light', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://unexistentHost:1026', + commands: [ + { + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }, + optionsDelete = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'DELETE', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }, + optionsUpdate = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'PUT', + json: { + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://anotherUnexistentHost:1026', + commands: [ + { + name: 'wheel1', + type: 'Wheel' + } + ], + lazy: [ + { + name: 'luminescence', + type: 'Lumens' + } + ], + active: [ + { + name: 'status', + type: 'Boolean' + } + ] + }, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }, + optionsList = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'GET', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/*' + } + }, + optionsGet = { + url: 'http://localhost:4041/iot/agents/testAgent', + method: 'GET', + json: {}, + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } + }, + iotAgentDb; + +describe('MongoDB Group Registry test', function() { + + beforeEach(function(done) { + iotAgentLib.activate(iotAgentConfig, function() { + mongo.connect('mongodb://localhost:27017/iotagent', function(err, db) { + iotAgentDb = db; + done(); + }); + }); + }); + + afterEach(function(done) { + iotAgentLib.deactivate(function() { + iotAgentDb.close(function(error) { + mongoUtils.cleanDbs(done); + }); + }); + }); + describe('When a new device group creation request arrives', function() { + it('should store it in the DB', function(done) { + request(optionsCreation, function(error, response, body) { + iotAgentDb.collection('groups').find({}).toArray(function(err, docs) { + should.not.exist(err); + should.exist(docs); + should.exist(docs.length); + docs.length.should.equal(1); + should.exist(docs[0].type); + should.exist(docs[0].apikey); + docs[0].type.should.equal('Light'); + docs[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732'); + done(); + }); + }); + }); + it('should store the service information from the headers into the DB', function(done) { + request(optionsCreation, function(error, response, body) { + iotAgentDb.collection('groups').find({}).toArray(function(err, docs) { + should.not.exist(err); + should.exist(docs[0].service); + should.exist(docs[0].subservice); + docs[0].service.should.equal('TestService'); + docs[0].subservice.should.equal('/testingPath'); + done(); + }); + }); + }); + }); + + describe('When a device group removal request arrives', function() { + beforeEach(function(done) { + request(optionsCreation, done); + }); + + it('should remove it from the database', function(done) { + request(optionsDelete, function(error, response, body) { + iotAgentDb.collection('groups').find({}).toArray(function(err, docs) { + should.not.exist(err); + should.exist(docs); + should.exist(docs.length); + docs.length.should.equal(0); + done(); + }); + }); + }); + }); + + describe('When a device group update request arrives', function() { + beforeEach(function(done) { + request(optionsCreation, done); + }); + + it('should update the values in the database', function(done) { + request(optionsUpdate, function(error, response, body) { + iotAgentDb.collection('groups').find({}).toArray(function(err, docs) { + should.not.exist(err); + should.exist(docs); + should.exist(docs[0].cbHost); + docs[0].cbHost.should.equal('http://anotherUnexistentHost:1026'); + done(); + }); + }); + }); + }); + + describe('When a device group listing request arrives', function() { + beforeEach(function(done) { + async.series([ + async.apply(request, optionsCreation), + async.apply(request, optionsCreation), + async.apply(request, optionsCreation) + ], done); + }); + + it('should return all the configured device groups from the database', function(done) { + request(optionsList, function(error, response, body) { + body.count.should.equal(3); + done(); + }); + }); + }); + + describe('When a device info request arrives', function() { + beforeEach(function(done) { + async.series([ + async.apply(request, optionsCreation) + ], done); + }); + + it('should return all the configured device groups from the database', function(done) { + request(optionsGet, function(error, response, body) { + body.service.should.equal('TestService'); + done(); + }); + }); + }); +}); From 184861662810763a0594cced8a269a79e382b72d Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Feb 2015 18:46:34 +0100 Subject: [PATCH 18/78] FIX Linter errors --- lib/services/groupRegistryMongoDB.js | 8 +++----- test/unit/mongodb-group-registry-test.js | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index 74c00445f..95f1deaae 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -26,8 +26,6 @@ var logger = require('fiware-node-logger'), dbService = require('../model/dbConn'), errors = require('../errors'), Group = require('../model/Group'), - _ = require('underscore'), - DEFAULT_DB_NAME = 'iotagent', context = { op: 'IoTAgentNGSI.MongoDBGroupRegister' }; @@ -128,15 +126,15 @@ function find(service, subservice, callback) { } else if (data) { callback(null, data); } else { - logger.debug(context, 'Device group [%s] not found.', id); + logger.debug(context, 'Device group for service [%s] and subservice [%s] not found.', service, subservice); - callback(new errors.DeviceGroupNotFound(id)); + callback(new errors.DeviceGroupNotFound(service, subservice)); } }); } function update(id, body, callback) { - getById(id, function (error, group) { + getById(id, function(error, group) { if (error) { callback(error); } else { diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index ad12995fc..fe5fdda05 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -24,7 +24,6 @@ var iotAgentLib = require('../../'), async = require('async'), - groupRegistryMemory = require('../../lib/services/groupRegistryMemory'), request = require('request'), should = require('should'), iotAgentConfig = { From b1982c078bb493a1ee319f25c07ef1ef4bc882c4 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Thu, 19 Feb 2015 17:51:58 +0100 Subject: [PATCH 19/78] ADD Configuration API Commands for IoT Agent tester --- bin/iotAgentTester.js | 234 +++++++++++++++++- .../deviceGroupAdministrationServer.js | 8 +- test/unit/device-group-api-test.js | 10 +- test/unit/mongodb-group-registry-test.js | 10 +- 4 files changed, 236 insertions(+), 26 deletions(-) diff --git a/bin/iotAgentTester.js b/bin/iotAgentTester.js index 2f1f06945..a55aa0184 100755 --- a/bin/iotAgentTester.js +++ b/bin/iotAgentTester.js @@ -35,6 +35,13 @@ var config = require('../config'), service: 'tester', subservice: '/test' }, + configIot = { + host: 'localhost', + port: 4041, + name: 'default', + service: 'tester', + subservice: '/test' + }, separator = '\n\n\t'; function queryContext(commands) { @@ -177,6 +184,27 @@ function configure(commands) { config.subservice = commands[3]; } +function showConfig(commands) { + console.log('\nCurrent configuration:\n\n'); + console.log(JSON.stringify(config, null, 4)); + console.log('\n'); + clUtils.prompt(); +} + +function configureIot(commands) { + configIot.host = commands[0]; + configIot.port = commands[1]; + config.service = commands[2]; + config.subservice = commands[3]; +} + +function showConfigIot(commands) { + console.log('\nCurrent configuration:\n\n'); + console.log(JSON.stringify(configIot, null, 4)); + console.log('\n'); + clUtils.prompt(); +} + function discoverContext(commands) { var options = { url: 'http://' + config.host + ':' + config.port + '/v1/registry/discoverContextAvailability', @@ -212,7 +240,7 @@ function discoverContext(commands) { function provisionDevice(commands) { function generateOptions(deviceConfig, callback) { var options = { - uri: 'http://' + commands[0] + ':' + commands[1] + '/iot/devices', + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/devices', method: 'POST' }; @@ -246,7 +274,7 @@ function provisionDevice(commands) { clUtils.prompt(); } - fs.readFile(commands[2], 'utf8', function (error, deviceConfig) { + fs.readFile(commands[0], 'utf8', function (error, deviceConfig) { if (error && error.code === 'ENOENT') { console.error('File not found'); clUtils.prompt(); @@ -259,11 +287,157 @@ function provisionDevice(commands) { }); } -function showConfig(commands) { - console.log('\nCurrent configuration:\n\n'); - console.log(JSON.stringify(config, null, 4)); - console.log('\n'); - clUtils.prompt(); +function listProvisioned(commands) { + var options = { + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/devices', + method: 'GET' + }; + + console.log('Devices provisioned in host [%s:%s]', configIot.host, configIot.port); + console.log('----------------------------------------------------------------'); + + request(options, function(error, result, body) { + if (error) { + console.log('Couldn\'t connect with the provisioning server: ' + error.toString()); + } else if (result.statusCode === 200 && body) { + var parsedBody = JSON.parse(body); + console.log(JSON.stringify(parsedBody, null, 4)); + } else { + console.log('Unexpected application error. Status: ' + result.statusCode); + } + clUtils.prompt(); + }); +} + +function removeProvisioned(commands) { + var options = { + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/devices/' + commands[0], + method: 'DELETE' + }; + + console.log('Removing device [%s] [%s:%s]', commands[0], configIot.host, configIot.port); + console.log('----------------------------------------------------------------'); + + request(options, function(error, result, body) { + if (error) { + console.log('Couldn\'t connect with the provisioning server: ' + error.toString()); + } else if (result.statusCode === 200 && body) { + var parsedBody = JSON.parse(body); + console.log('Device [%s] removed successfully', commands[0]); + } else { + console.log('Unexpected application error. Status: ' + result.statusCode); + } + clUtils.prompt(); + }); +} + +function addGroup(commands) { + console.log('Adding device groups to host [%s:%s] from file [%s]', configIot.host, configIot.port, commands[0]); + + function generateOptions(deviceConfig, callback) { + var options = { + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/agents/' + configIot.name + '/services', + method: 'POST', + headers: { + 'fiware-service': configIot.service, + 'fiware-servicepath': configIot.subservice + } + }; + + try { + var payload = JSON.parse(deviceConfig); + options.json = payload; + callback(null, options); + } catch (e) { + callback('Wrong JSON. Couldn\'t parse'); + } + } + + function sendProvisionRequest(options, callback) { + request(options, function(error, result, body) { + if (error) { + callback('Couldn\'t connect with the provisioning server: ' + error.toString()); + } else if (result.statusCode === 200 && body) { + callback(null, 'Device group successfully provisioned'); + } else { + callback('Unexpected application error. Status: ' + result.statusCode); + } + }); + } + + function handleOut(error, msg) { + if (error) { + console.error(error); + } else { + console.log(msg); + } + clUtils.prompt(); + } + + fs.readFile(commands[0], 'utf8', function (error, deviceConfig) { + if (error && error.code === 'ENOENT') { + console.error('File not found'); + clUtils.prompt(); + } else { + async.waterfall([ + async.apply(generateOptions, deviceConfig), + sendProvisionRequest + ], handleOut); + } + }); +} + +function removeGroup(commands) { + var options = { + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/agents/' + configIot.name + '/services', + method: 'DELETE', + headers: { + 'fiware-service': configIot.service, + 'fiware-servicepath': configIot.subservice + } + }; + + console.log('Removing device group for subservice [%s] from [%s:%s]', + configIot.subservice, configIot.host, configIot.port); + + console.log('----------------------------------------------------------------'); + + request(options, function(error, result, body) { + if (error) { + console.log('Couldn\'t connect with the provisioning server: ' + error.toString()); + } else if (result.statusCode === 200 && body) { + console.log('Device group for subservice [%s] removed successfully', configIot.subservice); + } else { + console.log('Unexpected application error. Status: ' + result.statusCode); + } + clUtils.prompt(); + }); +} + +function listGroups(commands) { + console.log('Devices groups provisioned in host [%s:%s]', configIot.host, configIot.port); + console.log('----------------------------------------------------------------'); + var options = { + uri: 'http://' + configIot.host + ':' + configIot.port + '/iot/agents/' + configIot.name + '/services', + method: 'GET', + headers: { + 'fiware-service': configIot.service, + 'fiware-servicepath': '/*' + } + }; + + request(options, function(error, result, body) { + console.log(JSON.stringify(result.headers)); + if (error) { + console.log('Couldn\'t connect with the provisioning server: ' + error.toString()); + } else if (result.statusCode === 200 && body) { + var parsedBody = JSON.parse(body); + console.log(JSON.stringify(parsedBody, null, 4)); + } else { + console.log('Unexpected application error. Status: ' + result.statusCode); + } + clUtils.prompt(); + }); } var commands = { @@ -294,21 +468,57 @@ var commands = { description: '\tGet all the context providers for a entity and type.', handler: discoverContext }, - 'config': { + 'configCb': { parameters: ['host', 'port', 'service', 'subservice'], description: '\tConfig a new host and port for the remote Context Broker.', handler: configure }, - 'showConfig': { + 'showConfigCb': { parameters: [], - description: '\tShow the current configuration of the client.', + description: '\tShow the current configuration of the client for the Context Broker.', handler: showConfig }, + 'configIot': { + parameters: ['host', 'port', 'service', 'subservice'], + description: '\tConfig a new host and port for the remote IoT Agent.', + handler: configureIot + }, + 'showConfigIot': { + parameters: [], + description: '\tShow the current configuration of the client for the IoT Agent.', + handler: showConfigIot + }, 'provision': { - parameters: ['host', 'port', 'filename'], + parameters: ['filename'], description: '\tProvision a new device using the Device Provisioning API. The device configuration is \n' + - '\tread from the script location.', + '\tread from the file specified in the "filename" parameter.', handler: provisionDevice + }, + 'listProvisioned': { + parameters: [], + description: '\tList all the provisioned devices in an IoT Agent.', + handler: listProvisioned + }, + 'removeProvisioned': { + parameters: ['deviceId'], + description: '\tRemove the selected provisioned device from the IoT Agent, specified by its Device ID.', + handler: removeProvisioned + }, + 'addGroup': { + parameters: ['filename'], + description: '\tAdd a new device group to the specified IoT Agent through the Configuration API. The \n' + + '\tbody is taken from the file specified in the "filename" parameter.', + handler: addGroup + }, + 'listGroups': { + parameters: [], + description: '\tList all the device groups created in the selected IoT Agent for the configured service', + handler: listGroups + }, + 'removeGroup': { + parameters: [], + description: '\tRemove the device group corresponding to the current configured subservice.', + handler: removeGroup } }; diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index e41122388..412bb8f37 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -174,10 +174,10 @@ function handleDeleteDeviceGroups(req, res, next) { * @param {Object} router Express request router object. */ function loadContextRoutes(router, name) { - router.post('/iot/agents/' + name, checkHeaders, checkBody(templateGroup), handleCreateDeviceGroup); - router.get('/iot/agents/' + name, checkHeaders, handleListDeviceGroups); - router.put('/iot/agents/' + name, checkHeaders, handleModifyDeviceGroups); - router.delete('/iot/agents/' + name, checkHeaders, handleDeleteDeviceGroups); + router.post('/iot/agents/' + name + '/services', checkHeaders, checkBody(templateGroup), handleCreateDeviceGroup); + router.get('/iot/agents/' + name + '/services', checkHeaders, handleListDeviceGroups); + router.put('/iot/agents/' + name + '/services', checkHeaders, handleModifyDeviceGroups); + router.delete('/iot/agents/' + name + '/services', checkHeaders, handleDeleteDeviceGroups); } exports.loadContextRoutes = loadContextRoutes; diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 0794f9069..d06b7a9b6 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -46,7 +46,7 @@ var iotAgentLib = require('../../'), throttling: 'PT5S' }, optionsCreation = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'POST', json: { services: [ @@ -83,7 +83,7 @@ var iotAgentLib = require('../../'), } }, optionsDelete = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'DELETE', json: {}, headers: { @@ -92,7 +92,7 @@ var iotAgentLib = require('../../'), } }, optionsUpdate = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'PUT', json: { trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', @@ -122,7 +122,7 @@ var iotAgentLib = require('../../'), } }, optionsList = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'GET', json: {}, headers: { @@ -131,7 +131,7 @@ var iotAgentLib = require('../../'), } }, optionsGet = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'GET', json: {}, headers: { diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index fe5fdda05..8191492d7 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -53,7 +53,7 @@ var iotAgentLib = require('../../'), mongo = require('mongodb').MongoClient, mongoUtils = require('./mongoDBUtils'), optionsCreation = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'POST', json: { services: [ @@ -90,7 +90,7 @@ var iotAgentLib = require('../../'), } }, optionsDelete = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'DELETE', json: {}, headers: { @@ -99,7 +99,7 @@ var iotAgentLib = require('../../'), } }, optionsUpdate = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'PUT', json: { trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', @@ -129,7 +129,7 @@ var iotAgentLib = require('../../'), } }, optionsList = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'GET', json: {}, headers: { @@ -138,7 +138,7 @@ var iotAgentLib = require('../../'), } }, optionsGet = { - url: 'http://localhost:4041/iot/agents/testAgent', + url: 'http://localhost:4041/iot/agents/testAgent/services', method: 'GET', json: {}, headers: { From e42ba58f9875eb91670d62f962f1db5bfa524d30 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Thu, 19 Feb 2015 17:57:04 +0100 Subject: [PATCH 20/78] Update README.md --- README.md | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6dcda03b3..ecd29c61f 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,9 @@ listdevices List all the devices that have been registered in this IoT Agent session ``` ### Agent tester -The library also offers a Context Broker client that can be used to simulate queries and other operations to the Context Broker used by the IoT Agent, triggering Context Provider forwardings for lazy attributes and checking the appropriate values for active ones. +The library also offers a Context Broker and IoT Agent client that can be used to: +* Simulate operations to the Context Broker used by the IoT Agent, triggering Context Provider forwardings for lazy attributes and checking the appropriate values for active ones. +* Simulate operations to the Device Provisioning API and Configuration API of the IoT Agent. The tester can be started with the following command, from the root folder of the project: ``` @@ -314,19 +316,50 @@ discover Get all the context providers for a entity and type. -config +configCb Config a new host and port for the remote Context Broker. -showConfig +showConfigCb - Show the current configuration of the client. + Show the current configuration of the client for the Context Broker. -provision +configIot + + Config a new host and port for the remote IoT Agent. + +showConfigIot + + Show the current configuration of the client for the IoT Agent. + +provision Provision a new device using the Device Provisioning API. The device configuration is - read from the script location. + read from the file specified in the "filename" parameter. + +listProvisioned + + List all the provisioned devices in an IoT Agent. + +removeProvisioned + + Remove the selected provisioned device from the IoT Agent, specified by its Device ID. + +addGroup + + Add a new device group to the specified IoT Agent through the Configuration API. The + body is taken from the file specified in the "filename" parameter. + +listGroups + + List all the device groups created in the selected IoT Agent for the configured service + +removeGroup + + Remove the device group corresponding to the current configured subservice. ``` +The agent session stores transient configuration data about the target Context Broker and the target IoT Agent. This configuration is independent, and can be checked with the `showConfigCb` and `showConfigIot` commands, respectively. Their values can be changed with the `configCb` and `configIot` commands respectively. The new configurations will be deleted upon startup. + ## Configuration The `activate()` function that starts the IoT Agent receives as single parameter with the configuration for the IoT Agent. The Agent Console reads the same configuration from the `config.js` file. From b8c0f93db646b23d1d9e80e4aacd67f10e2e80cd Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Thu, 19 Feb 2015 17:58:44 +0100 Subject: [PATCH 21/78] ADD Release changes --- CHANGES_NEXT_RELEASE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 4e61dcea6..29094a641 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1 +1,3 @@ -- ADD Keywords to the package.json. \ No newline at end of file +- ADD Keywords to the package.json. +- ADD Configuration API for dynamically creating configuration groups. +- ADD Device Provisioning API and Device Configuration API commands in the testing clients. From 68e76fb27146ba4a27f1c3e9496c06a29dbbb6fd Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 12:34:06 +0100 Subject: [PATCH 22/78] ADD Check presence of mandatory headers for the Device Provisioning API --- examples/deviceGroup.json | 29 +++++++++++++++ .../deviceGroupAdministrationServer.js | 36 +++++-------------- lib/services/deviceProvisioningServer.js | 14 +++++--- lib/services/restUtils.js | 30 +++++++++++++++- test/unit/device-provisioning-api_test.js | 31 +++++++++++++++- test/unit/listProvisionedDevices-test.js | 20 +++++++++++ test/unit/removeProvisionedDevice-test.js | 20 +++++++++++ test/unit/updateProvisionedDevices-test.js | 24 +++++++++++++ 8 files changed, 170 insertions(+), 34 deletions(-) create mode 100644 examples/deviceGroup.json diff --git a/examples/deviceGroup.json b/examples/deviceGroup.json new file mode 100644 index 000000000..58445d659 --- /dev/null +++ b/examples/deviceGroup.json @@ -0,0 +1,29 @@ +{ + "services": [ + { + "resource": "/deviceTest", + "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732", + "type": "Light", + "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS", + "cbHost": "http://unexistentHost:1026", + "commands": [ + { + "name": "wheel1", + "type": "Wheel" + } + ], + "lazy": [ + { + "name": "luminescence", + "type": "Lumens" + } + ], + "active": [ + { + "name": "status", + "type": "Boolean" + } + ] + } + ] +} diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index e41122388..741adcb02 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -24,6 +24,7 @@ var logger = require('fiware-node-logger'), errors = require('../errors'), + restUtils = require('./restUtils'), groupService = require('./groupService'), _ = require('underscore'), revalidator = require('revalidator'), @@ -36,29 +37,6 @@ var logger = require('fiware-node-logger'), 'fiware-servicepath' ]; -/** - * Checks for the pressence of all the mandatory headers, returning a BAD_REQUEST error if any one is not found. - * - * @param {Object} req Incoming request. - * @param {Object} res Outgoing response. - * @param {Function} next Invokes the next middleware in the chain. - */ -function checkHeaders(req, res, next) { - var headerKeys = _.keys(req.headers), - missing = []; - - for (var i = 0; i < mandatoryHeaders.length; i++) { - if (headerKeys.indexOf(mandatoryHeaders[i]) < 0) { - missing.push(mandatoryHeaders[i]); - } - } - - if (missing.length !== 0) { - next(new errors.MissingHeaders(JSON.stringify(missing))); - } else { - next(); - } -} /** * Generates a middleware that checks the request body against the given revalidator template. @@ -174,10 +152,14 @@ function handleDeleteDeviceGroups(req, res, next) { * @param {Object} router Express request router object. */ function loadContextRoutes(router, name) { - router.post('/iot/agents/' + name, checkHeaders, checkBody(templateGroup), handleCreateDeviceGroup); - router.get('/iot/agents/' + name, checkHeaders, handleListDeviceGroups); - router.put('/iot/agents/' + name, checkHeaders, handleModifyDeviceGroups); - router.delete('/iot/agents/' + name, checkHeaders, handleDeleteDeviceGroups); + router.post('/iot/agents/' + name, + restUtils.checkHeaders(mandatoryHeaders), checkBody(templateGroup), handleCreateDeviceGroup); + router.get('/iot/agents/' + name, + restUtils.checkHeaders(mandatoryHeaders), handleListDeviceGroups); + router.put('/iot/agents/' + name, + restUtils.checkHeaders(mandatoryHeaders), handleModifyDeviceGroups); + router.delete('/iot/agents/' + name, + restUtils.checkHeaders(mandatoryHeaders), handleDeleteDeviceGroups); } exports.loadContextRoutes = loadContextRoutes; diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index 1716f0b41..f9a00ccb1 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -32,6 +32,10 @@ var async = require('async'), op: 'IoTAgentNGSI.DeviceProvisioning' }, apply = async.apply, + mandatoryHeaders = [ + 'fiware-service', + 'fiware-servicepath' + ], provisioningAPITranslation = { /* jshint camelcase:false */ @@ -188,11 +192,11 @@ function handleUpdateDevice(req, res, next) { * @param {Object} router Express request router object. */ function loadContextRoutes(router) { - router.post('/iot/devices', handleProvision); - router.get('/iot/devices', handleListDevices); - router.get('/iot/devices/:deviceId', handleGetDevice); - router.put('/iot/devices/:deviceId', handleUpdateDevice); - router.delete('/iot/devices/:deviceId', handleRemoveDevice); + router.post('/iot/devices', restUtils.checkHeaders(mandatoryHeaders), handleProvision); + router.get('/iot/devices', restUtils.checkHeaders(mandatoryHeaders), handleListDevices); + router.get('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleGetDevice); + router.put('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleUpdateDevice); + router.delete('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleRemoveDevice); } exports.loadContextRoutes = loadContextRoutes; diff --git a/lib/services/restUtils.js b/lib/services/restUtils.js index 0b5d8ef8e..6882b37ca 100644 --- a/lib/services/restUtils.js +++ b/lib/services/restUtils.js @@ -22,7 +22,8 @@ */ 'use strict'; -var errors = require('../errors'); +var errors = require('../errors'), + _ = require('underscore'); /** * Checks all the mandatory attributes in the selected array are present in the presented body object. @@ -86,5 +87,32 @@ function xmlRawBody(req, res, next) { } } +/** + * Generates a Express middleware that checks for the pressence of all the mandatory headers, returning a BAD_REQUEST + * error if any one is not found. + * + * @param {Array} mandatoryHeaders List of mandatory headers. + * @returns {Function} next Function that check the pressence of the required headers + */ +function checkHeaders(mandatoryHeaders) { + return function headerChecker(req, res, next) { + var headerKeys = _.keys(req.headers), + missing = []; + + for (var i = 0; i < mandatoryHeaders.length; i++) { + if (headerKeys.indexOf(mandatoryHeaders[i]) < 0) { + missing.push(mandatoryHeaders[i]); + } + } + + if (missing.length !== 0) { + next(new errors.MissingHeaders(JSON.stringify(missing))); + } else { + next(); + } + }; +} + exports.checkMandatoryQueryParams = checkMandatoryQueryParams; exports.xmlRawBody = xmlRawBody; +exports.checkHeaders = checkHeaders; diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index b76320f25..20b32747d 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -71,7 +71,11 @@ describe('Device provisioning API: Provision devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', - json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json'), + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + } }; it('should add the device to the devices list', function(done) { @@ -120,6 +124,10 @@ describe('Device provisioning API: Provision devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionDeviceMissingParameters.json') }; @@ -141,6 +149,10 @@ describe('Device provisioning API: Provision devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/newBaseRoot/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }; @@ -167,4 +179,21 @@ describe('Device provisioning API: Provision devices', function() { }); }); }); + describe('When a device provisioning request without the mandatory headers arrives to the Agent', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + headers: {}, + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionDeviceMissingParameters.json') + }; + + it('should raise a MISSING_HEADERS error, indicating the missing attributes', function(done) { + request(options, function(error, response, body) { + should.exist(body); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); }); diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index 6d329cb66..ab73619cf 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -51,11 +51,19 @@ describe('Device provisioning API: List provisioned devices', function() { var provisioning1Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, provisioning2Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -98,6 +106,10 @@ describe('Device provisioning API: List provisioned devices', function() { describe('When a request for the list of provisioned devices arrive', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; @@ -114,6 +126,10 @@ describe('Device provisioning API: List provisioned devices', function() { describe('When a request for the information about a specific device arrives', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; @@ -135,6 +151,10 @@ describe('Device provisioning API: List provisioned devices', function() { describe('When a request for an unexistent device arrives', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light84', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; diff --git a/test/unit/removeProvisionedDevice-test.js b/test/unit/removeProvisionedDevice-test.js index dd1772bf7..a82d753b1 100644 --- a/test/unit/removeProvisionedDevice-test.js +++ b/test/unit/removeProvisionedDevice-test.js @@ -51,11 +51,19 @@ describe('Device provisioning API: Remove provisioned devices', function() { var provisioning1Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, provisioning2Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -106,6 +114,10 @@ describe('Device provisioning API: Remove provisioned devices', function() { describe('When a request to remove a provision device arrives', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'DELETE' }; @@ -121,6 +133,10 @@ describe('Device provisioning API: Remove provisioned devices', function() { request(options, function(error, response, body) { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; @@ -136,6 +152,10 @@ describe('Device provisioning API: Remove provisioned devices', function() { request(options, function(error, response, body) { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; diff --git a/test/unit/updateProvisionedDevices-test.js b/test/unit/updateProvisionedDevices-test.js index 9a06b8e55..661bd252f 100644 --- a/test/unit/updateProvisionedDevices-test.js +++ b/test/unit/updateProvisionedDevices-test.js @@ -51,11 +51,19 @@ describe('Device provisioning API: Update provisioned devices', function() { var provisioning1Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, provisioning2Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -109,6 +117,10 @@ describe('Device provisioning API: Update provisioned devices', function() { var optionsUpdate = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', method: 'PUT', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/updateProvisionDevice.json') }; @@ -124,6 +136,10 @@ describe('Device provisioning API: Update provisioned devices', function() { request(optionsUpdate, function(error, response, body) { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; @@ -142,6 +158,10 @@ describe('Device provisioning API: Update provisioned devices', function() { request(optionsUpdate, function(error, response, body) { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, method: 'GET' }; @@ -160,6 +180,10 @@ describe('Device provisioning API: Update provisioned devices', function() { var optionsUpdate = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', method: 'PUT', + headers: { + 'fiware-service': 'TestService', + 'fiware-servicepath': '/testingPath' + }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/updateProvisionDeviceWithId.json') }; From af9ee4ba3dcf5c31b0baf3f424f3734acb0b3dbe Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 12:46:18 +0100 Subject: [PATCH 23/78] ADD Extract service and subservice from headers --- lib/services/deviceProvisioningServer.js | 8 +++--- test/unit/device-provisioning-api_test.js | 26 ++++++++++++++----- test/unit/listProvisionedDevices-test.js | 24 ++++++++--------- test/unit/removeProvisionedDevice-test.js | 26 +++++++++---------- test/unit/updateProvisionedDevices-test.js | 30 +++++++++++----------- 5 files changed, 63 insertions(+), 51 deletions(-) diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index f9a00ccb1..3c0747bd6 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -65,14 +65,14 @@ function handleProvision(req, res, next) { } } - function registerDevice(body, callback) { + function registerDevice(service, subservice, body, callback) { /*jshint sub:true */ ngsi.register({ id: body['name'], type: body['entity_type'], name: body['entity_name'], - service: body['service'], - subservice: body['service_path'], + service: service, + subservice: subservice, active: body['attributes'], staticAttributes: body['static_attributes'], lazy: body['commands'], @@ -88,7 +88,7 @@ function handleProvision(req, res, next) { async.waterfall([ apply(restUtils.checkMandatoryQueryParams, ['name', 'entity_type', 'service', 'service_path', 'commands'], req.body), - registerDevice + apply(registerDevice, req.headers['fiware-service'], req.headers['fiware-servicepath']) ], handleProvisioningFinish); } diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index 20b32747d..a297147ec 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -51,7 +51,7 @@ describe('Device provisioning API: Provision devices', function() { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) @@ -73,8 +73,8 @@ describe('Device provisioning API: Provision devices', function() { method: 'POST', json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json'), headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' } }; @@ -119,14 +119,26 @@ describe('Device provisioning API: Provision devices', function() { }); }); }); + it('should store service and subservice info from the headers along with the device data', function(done) { + request(options, function(error, response, body) { + response.statusCode.should.equal(200); + iotAgentLib.listDevices(function(error, results) { + should.exist(results[0].service); + results[0].service.should.equal('smartGondor'); + should.exist(results[0].subservice); + results[0].subservice.should.equal('/gardens'); + done(); + }); + }); + }); }); describe('When a device provisioning request with missing data arrives to the IoT Agent', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionDeviceMissingParameters.json') }; @@ -150,8 +162,8 @@ describe('Device provisioning API: Provision devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/newBaseRoot/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }; diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index ab73619cf..8a1c74676 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -52,8 +52,8 @@ describe('Device provisioning API: List provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, @@ -61,8 +61,8 @@ describe('Device provisioning API: List provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -71,7 +71,7 @@ describe('Device provisioning API: List provisioned devices', function() { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) @@ -81,7 +81,7 @@ describe('Device provisioning API: List provisioned devices', function() { contextBrokerMock .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice2.json')) @@ -107,8 +107,8 @@ describe('Device provisioning API: List provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; @@ -127,8 +127,8 @@ describe('Device provisioning API: List provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; @@ -152,8 +152,8 @@ describe('Device provisioning API: List provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light84', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; diff --git a/test/unit/removeProvisionedDevice-test.js b/test/unit/removeProvisionedDevice-test.js index a82d753b1..4c5a64bff 100644 --- a/test/unit/removeProvisionedDevice-test.js +++ b/test/unit/removeProvisionedDevice-test.js @@ -52,8 +52,8 @@ describe('Device provisioning API: Remove provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, @@ -61,8 +61,8 @@ describe('Device provisioning API: Remove provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -71,7 +71,7 @@ describe('Device provisioning API: Remove provisioned devices', function() { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) @@ -81,7 +81,7 @@ describe('Device provisioning API: Remove provisioned devices', function() { contextBrokerMock .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice2.json')) @@ -91,7 +91,7 @@ describe('Device provisioning API: Remove provisioned devices', function() { contextBrokerMock .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext') .reply(200, utils.readExampleFile( @@ -115,8 +115,8 @@ describe('Device provisioning API: Remove provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'DELETE' }; @@ -134,8 +134,8 @@ describe('Device provisioning API: Remove provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; @@ -153,8 +153,8 @@ describe('Device provisioning API: Remove provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; diff --git a/test/unit/updateProvisionedDevices-test.js b/test/unit/updateProvisionedDevices-test.js index 661bd252f..56ee9569b 100644 --- a/test/unit/updateProvisionedDevices-test.js +++ b/test/unit/updateProvisionedDevices-test.js @@ -52,8 +52,8 @@ describe('Device provisioning API: Update provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') }, @@ -61,8 +61,8 @@ describe('Device provisioning API: Update provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; @@ -71,7 +71,7 @@ describe('Device provisioning API: Update provisioned devices', function() { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) @@ -81,7 +81,7 @@ describe('Device provisioning API: Update provisioned devices', function() { contextBrokerMock .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/registerProvisionedDevice2.json')) @@ -91,7 +91,7 @@ describe('Device provisioning API: Update provisioned devices', function() { contextBrokerMock .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') + .matchHeader('fiware-servicepath', '/gardens') .post('/NGSI9/registerContext', utils.readExampleFile( './test/unit/contextAvailabilityRequests/updateIoTAgent2.json')) @@ -118,8 +118,8 @@ describe('Device provisioning API: Update provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', method: 'PUT', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/updateProvisionDevice.json') }; @@ -137,8 +137,8 @@ describe('Device provisioning API: Update provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; @@ -159,8 +159,8 @@ describe('Device provisioning API: Update provisioned devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, method: 'GET' }; @@ -181,8 +181,8 @@ describe('Device provisioning API: Update provisioned devices', function() { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1', method: 'PUT', headers: { - 'fiware-service': 'TestService', - 'fiware-servicepath': '/testingPath' + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/updateProvisionDeviceWithId.json') }; From 6c54758c07fe7521e113f36222588153dcd1cf76 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 12:47:20 +0100 Subject: [PATCH 24/78] FIX Linter errors --- lib/services/deviceGroupAdministrationServer.js | 1 - lib/services/restUtils.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 741adcb02..c84a8de6d 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -26,7 +26,6 @@ var logger = require('fiware-node-logger'), errors = require('../errors'), restUtils = require('./restUtils'), groupService = require('./groupService'), - _ = require('underscore'), revalidator = require('revalidator'), templateGroup = require('../templates/deviceGroup.json'), context = { diff --git a/lib/services/restUtils.js b/lib/services/restUtils.js index 6882b37ca..65aba4bb0 100644 --- a/lib/services/restUtils.js +++ b/lib/services/restUtils.js @@ -92,7 +92,7 @@ function xmlRawBody(req, res, next) { * error if any one is not found. * * @param {Array} mandatoryHeaders List of mandatory headers. - * @returns {Function} next Function that check the pressence of the required headers + * @return {Function} next Function that check the pressence of the required headers */ function checkHeaders(mandatoryHeaders) { return function headerChecker(req, res, next) { From fc39a501c4b4a485e440c9abbd37773c734a56cc Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 12:57:06 +0100 Subject: [PATCH 25/78] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6dcda03b3..a3e70a01a 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,8 @@ These are the parameters that can be configured in the global section: ### Overview The IoT Agents offer a provisioning API where devices can be preregistered, so all the information about service and subservice mapping, security information and attribute configuration can be specified in a per device way instead of relaying on the type configuration. The following section specified the format of the device payload; this will be the payload accepted by all the write operations and that will be returned by all the read operations. +Two parameters in this payload are given a special treatment: service and subservice. This two parameters are needed to fill the `fiware-service` and `fiware-servicepath` mandatory headers that will be used in the interactions with the Context Broker. This parameters should not be passed along with the rest of the body, but they will be taken from the same headers, as received by the Device Provisioning API (this two headers are, thus, mandatory both for incoming and outgoing requests). + ### Device model | Attribute | Definition | Example of value | | ------------------- |:---------------------------------------------- |:------------------------------------- | From cd654159ae81c318c1cb34246d0f24634a94c044 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 13:38:34 +0100 Subject: [PATCH 26/78] FIX Context Broker attribute name --- lib/services/ngsiService.js | 10 +++---- test/unit/active-devices-test.js | 5 +--- test/unit/device-group-api-test.js | 47 +++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 2c9875be0..41443c8e5 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -218,8 +218,7 @@ function registerDevice(deviceObj, callback) { * @param {String} token User token to identify against the PEP Proxies (optional). */ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, token, callback) { - var brokerHost = config.contextBroker.host, - brokerPort = config.contextBroker.port, + var cbHost = 'http://' + config.contextBroker.host + ':' + config.contextBroker.port, options, headers = { 'fiware-service': config.service, @@ -236,14 +235,13 @@ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, toke headers['fiware-servicepath'] = typeInformation.subservice; } - if (typeInformation.contextBroker) { - brokerHost = typeInformation.contextBroker.host; - brokerPort = typeInformation.contextBroker.port; + if (typeInformation.cbHost) { + cbHost = typeInformation.cbHost; } } options = { - url: 'http://' + brokerHost + ':' + brokerPort + '/v1/updateContext', + url: cbHost + '/v1/updateContext', method: 'POST', json: { contextElements: [ diff --git a/test/unit/active-devices-test.js b/test/unit/active-devices-test.js index 277533b30..c8263b687 100644 --- a/test/unit/active-devices-test.js +++ b/test/unit/active-devices-test.js @@ -64,10 +64,7 @@ var iotAgentLib = require('../../'), ] }, 'Humidity': { - contextBroker: { - host: '192.168.1.1', - port: '3024' - }, + cbHost: 'http://192.168.1.1:3024', commands: [], lazy: [], active: [ diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index d06b7a9b6..7bcd7d21e 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -24,6 +24,8 @@ var iotAgentLib = require('../../'), async = require('async'), + nock = require('nock'), + utils = require('../tools/utils'), groupRegistryMemory = require('../../lib/services/groupRegistryMemory'), request = require('request'), should = require('should'), @@ -53,7 +55,7 @@ var iotAgentLib = require('../../'), { resource: '/deviceTest', apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732', - type: 'Light', + type: 'SensorMachine', trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', cbHost: 'http://unexistentHost:1026', commands: [ @@ -184,7 +186,7 @@ describe('Device Group Configuration API', function() { request(optionsCreation, function(error, response, body) { /* jshint sub:true */ - should.exist(iotAgentConfig.types['Light']); + should.exist(iotAgentConfig.types['SensorMachine']); done(); }); }); @@ -267,7 +269,7 @@ describe('Device Group Configuration API', function() { request(optionsDelete, function(error, response, body) { /* jshint sub:true */ - should.not.exist(iotAgentConfig.types['Light']); + should.not.exist(iotAgentConfig.types['SensorMachine']); done(); }); }); @@ -318,7 +320,7 @@ describe('Device Group Configuration API', function() { request(optionsUpdate, function(error, response, body) { /* jshint sub:true */ - iotAgentConfig.types['Light'].cbHost.should.equal('http://anotherUnexistentHost:1026'); + iotAgentConfig.types['SensorMachine'].cbHost.should.equal('http://anotherUnexistentHost:1026'); done(); }); }); @@ -388,4 +390,41 @@ describe('Device Group Configuration API', function() { }); }); }); + describe('When a new device from a created group arrives to the IoT Agent and sends a measure', function() { + var contextBrokerMock, + values = [ + { + name: 'status', + type: 'String', + value: 'STARTING' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://unexistentHost:1026') + .matchHeader('fiware-service', 'TestService') + .matchHeader('fiware-servicepath', '/testingPath') + .post('/v1/updateContext', + utils.readExampleFile('./test/unit/contextRequests/updateContext3.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextResponses/updateContext1Success.json')); + + done(); + }); + + afterEach(function(done){ + nock.cleanAll(); + done(); + }); + + it('should use the configured data', function(done) { + iotAgentLib.update('machine1', 'SensorMachine', values, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); }); From 4506612e2984ed359d5cfc73d1219c87e46962f8 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 13:39:15 +0100 Subject: [PATCH 27/78] FIX Linter errors --- test/unit/device-group-api-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 7bcd7d21e..51e5106db 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -414,7 +414,7 @@ describe('Device Group Configuration API', function() { done(); }); - afterEach(function(done){ + afterEach(function(done) { nock.cleanAll(); done(); }); From 35a02337f880314d48a7667dde1c557bdfacc775 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 13:41:45 +0100 Subject: [PATCH 28/78] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 12957c7ce..9d37eb073 100644 --- a/README.md +++ b/README.md @@ -572,14 +572,14 @@ Returns: ### Type Configuration The IoT Agent can be configured to expect certain kinds of devices, with preconfigured sets of attributes, service information, security information and other attributes. The `types` attribute of the configuration is a map, where the key is the type name and the value is an object containing all the type information. Each type can has the following information configured: -* service: service of the devices of this type. -* subservice: subservice of the devices of this type. -* active: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. -* lazy: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. -* commands: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. -* internalAttributes: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. -* trust: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). -* contextBroker: Context Broker connection information. This options can be used to override the global ones for specific types of devices. +* **service**: service of the devices of this type. +* **subservice**: subservice of the devices of this type. +* **active**: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. +* **lazy**: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. +* **commands**: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. +* **internalAttributes**: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. +* **trust**: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). +* **cbHost**: Context Broker host url. This option can be used to override the global CB configuration for specific types of devices. ## Secured access to the Context Broker For access to instances of the Context Broker secured with a [PEP Proxy](https://github.com/telefonicaid/fiware-orion-pep), an authentication mechanism based in Keystone Trust tokens is provided. A Trust token is a long-term token that can be issued by any user to give another user permissions to impersonate him with a given role in a given project. From 8aa65f28ea86149e1d087a83ad02b4756060dae5 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 14:54:17 +0100 Subject: [PATCH 29/78] ADD Limit parameter --- lib/services/deviceProvisioningServer.js | 2 +- lib/services/deviceRegistryMemory.js | 12 +++- lib/services/deviceRegistryMongoDB.js | 12 +++- lib/services/ngsiService.js | 14 +++- test/unit/contextRequests/updateContext3.json | 17 +++++ test/unit/listProvisionedDevices-test.js | 64 ++++++++++++++++++- 6 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 test/unit/contextRequests/updateContext3.json diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index 3c0747bd6..dfd7c42c6 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -96,7 +96,7 @@ function handleProvision(req, res, next) { * Express middleware that retrieves the complete set of provisioned devices (in JSON format). */ function handleListDevices(req, res, next) { - ngsi.listDevices(function handleListDevices(error, deviceList) { + ngsi.listDevices(req.query.limit, req.query.offset, function handleListDevices(error, deviceList) { if (error) { next(error); } else { diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 488f7aea6..0c2c81845 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -56,14 +56,22 @@ function removeDevice(id, callback) { } /** - * Return the list of currently registered devices (via callback). + * Return the list of currently registered devices (via callback). This function can be invoked in two different ways: + * with just one parameter (the callback) or with three parameters (including limit and offset). + * + * @param {Number} limit Maximum number of entries to return. + * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(callback) { +function listDevices(limit, offset, callback) { var result = []; for (var i in registeredDevices) { if (registeredDevices.hasOwnProperty(i)) { result.push(registeredDevices[i]); + + if (limit && result.length === parseInt(limit)) { + break; + } } } callback(null, result); diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index 6bc2f5da1..bee6e213d 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -95,11 +95,17 @@ function removeDevice(id, callback) { /** * Return the list of currently registered devices (via callback). */ -function listDevices(callback) { +function listDevices(limit, offset, callback) { var condition = {}, - query; + query = Device.model.find(condition).sort(); - query = Device.model.find(condition).sort(); + if (limit) { + query.limit(parseInt(limit)); + } + + if (offset) { + query.skip(parseInt(offset)); + } query.exec(callback); } diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 41443c8e5..f611e354f 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -389,11 +389,19 @@ function updateRegisterDevice(deviceObj, callback) { } /** - * Return a list of all the devices registered in the system. + * Return a list of all the devices registered in the system. This function can be invoked in two different ways: + * with just one parameter (the callback) or with three parameters (including limit and offset). + * + * @param {Number} limit Maximum number of entries to return. + * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(callback) { +function listDevices(limit, offset, callback) { + if (!callback) { + callback = limit; + } + if (registry) { - registry.list(callback); + registry.list(limit, offset, callback); } else { logger.error(context, 'Tried to list devices before a registry was available'); callback(new errors.RegistryNotAvailable()); diff --git a/test/unit/contextRequests/updateContext3.json b/test/unit/contextRequests/updateContext3.json new file mode 100644 index 000000000..b9237f120 --- /dev/null +++ b/test/unit/contextRequests/updateContext3.json @@ -0,0 +1,17 @@ +{ + "contextElements": [ + { + "type": "SensorMachine", + "isPattern": "false", + "id": "machine1", + "attributes": [ + { + "name": "status", + "type": "String", + "value": "STARTING" + } + ] + } + ], + "updateAction": "APPEND" +} \ No newline at end of file diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index 8a1c74676..816e15f42 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -48,7 +48,11 @@ var iotAgentLib = require('../../'), }; describe('Device provisioning API: List provisioned devices', function() { - var provisioning1Options = { + var provisioning1Options, + provisioning2Options; + + beforeEach(function(done) { + provisioning1Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', headers: { @@ -56,7 +60,8 @@ describe('Device provisioning API: List provisioned devices', function() { 'fiware-servicepath': '/gardens' }, json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') - }, + }; + provisioning2Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', @@ -67,7 +72,6 @@ describe('Device provisioning API: List provisioned devices', function() { json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionAnotherDevice.json') }; - beforeEach(function(done) { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') @@ -166,4 +170,58 @@ describe('Device provisioning API: List provisioned devices', function() { }); }); }); + describe('When a request for listing all the devices with a limit of 3 arrives', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices?limit=3', + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + }, + method: 'GET' + }; + + function createDeviceRequest(i, callback) { + var provisioningDeviceOptions = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + }, + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') + }; + + provisioningDeviceOptions.json.name = provisioningDeviceOptions.json.name + '_' + i; + + request(provisioningDeviceOptions, callback); + } + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/NGSI9/registerContext') + .times(10) + .reply(200, + utils.readExampleFile( + './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); + + iotAgentLib.clearAll(function() { + async.times(10, createDeviceRequest, function(error, results) { + done(); + }); + }); + }); + + it('should return just 3 devices', function(done) { + request(options, function(error, response, body) { + var parsedBody = JSON.parse(body); + should.not.exist(error); + parsedBody.length.should.equal(3); + done(); + }); + }); + }); }); From 1bf84db7d607306023e83bb8d86d996e3310a3d1 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 15:02:02 +0100 Subject: [PATCH 30/78] ADD Offset features to the Provisoning API --- lib/services/deviceRegistryMemory.js | 9 +++- test/unit/listProvisionedDevices-test.js | 59 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 0c2c81845..10fe0b28f 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -63,11 +63,16 @@ function removeDevice(id, callback) { * @param {Number} offset Number of entries to skip for pagination. */ function listDevices(limit, offset, callback) { - var result = []; + var result = [], + skipped = 0; for (var i in registeredDevices) { if (registeredDevices.hasOwnProperty(i)) { - result.push(registeredDevices[i]); + if (offset && skipped < offset) { + skipped++; + } else { + result.push(registeredDevices[i]); + } if (limit && result.length === parseInt(limit)) { break; diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index 816e15f42..813f4132a 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -224,4 +224,63 @@ describe('Device provisioning API: List provisioned devices', function() { }); }); }); + + describe('When a request for listing all the devices with a offset of 3 arrives', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices?offset=3', + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + }, + method: 'GET' + }; + + function createDeviceRequest(i, callback) { + var provisioningDeviceOptions = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + }, + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json') + }; + + provisioningDeviceOptions.json.name = provisioningDeviceOptions.json.name + '_' + i; + + request(provisioningDeviceOptions, callback); + } + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/NGSI9/registerContext') + .times(10) + .reply(200, + utils.readExampleFile( + './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); + + iotAgentLib.clearAll(function() { + async.times(10, createDeviceRequest, function(error, results) { + done(); + }); + }); + }); + + it('should skip the first 3 devices', function(done) { + request(options, function(error, response, body) { + var parsedBody = JSON.parse(body); + should.not.exist(error); + + for (var i = 0; i < parsedBody.length; i++) { + ['Light1_0', 'Light1_1', 'Light1_2'].indexOf(parsedBody[i].id).should.equal(-1); + } + + done(); + }); + }); + }); }); From 79f0351956563b15cc120e01d13fa70d75f9aba4 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 15:03:58 +0100 Subject: [PATCH 31/78] FIX Linter errors --- lib/services/deviceRegistryMemory.js | 4 ++-- lib/services/deviceRegistryMongoDB.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 10fe0b28f..62c0294ca 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -68,13 +68,13 @@ function listDevices(limit, offset, callback) { for (var i in registeredDevices) { if (registeredDevices.hasOwnProperty(i)) { - if (offset && skipped < offset) { + if (offset && skipped < parseInt(offset, 10)) { skipped++; } else { result.push(registeredDevices[i]); } - if (limit && result.length === parseInt(limit)) { + if (limit && result.length === parseInt(limit, 10)) { break; } } diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index bee6e213d..f72047377 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -100,11 +100,11 @@ function listDevices(limit, offset, callback) { query = Device.model.find(condition).sort(); if (limit) { - query.limit(parseInt(limit)); + query.limit(parseInt(limit, 10)); } if (offset) { - query.skip(parseInt(offset)); + query.skip(parseInt(offset, 10)); } query.exec(callback); From 24d839222deec5fa1e3deec4429d0d867bfc87b9 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 20 Feb 2015 15:06:57 +0100 Subject: [PATCH 32/78] Update README.md --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d37eb073..45ccb5349 100644 --- a/README.md +++ b/README.md @@ -222,11 +222,13 @@ In the case of NGSI requests affecting multiple entities, this handler will be c ###### Signature ``` function listDevices(callback) +function listDevices(limit, offset, callback) ``` ###### Description -Return a list of all the devices registered in the system. +Return a list of all the devices registered in the system. If invoked with three parameters, it limits the number of devices to return and the first device to be returned. ###### Params - +* limit: maximum number of devices to return in the results. +* offset: number of results to skip before returning the results. ##### iotagentLib.getDevice() ###### Signature ``` @@ -435,6 +437,10 @@ Returns: #### GET /iot/devices Returns a list of all the devices in the device registry with all its data. +Query parameters: +* limit: if present, limits the number of devices returned in the list. +* offset: if present, skip that number of devices from the original query. + Returns: * 200 OK if successful, and the selected Device payload in JSON format. * 404 NOT FOUND if the device was not found in the database. From c3191572ae9dfe2dae1fb863098853ea5400cf71 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Feb 2015 09:45:47 +0100 Subject: [PATCH 33/78] ADD Changes to Change List --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 29094a641..6346f7c11 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,3 +1,4 @@ - ADD Keywords to the package.json. - ADD Configuration API for dynamically creating configuration groups. - ADD Device Provisioning API and Device Configuration API commands in the testing clients. +- ADD Pagination in the Device List operations. From e5a08382245f58921c9d0386e1edf92c583b0149 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 12:09:25 +0100 Subject: [PATCH 34/78] ADD StaticAttributes field to the model --- lib/model/Device.js | 1 + lib/services/deviceRegistryMongoDB.js | 2 +- lib/services/ngsiService.js | 2 +- test/unit/mongodb-registry-test.js | 9 +++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/model/Device.js b/lib/model/Device.js index b29bddb2a..70ca630a6 100644 --- a/lib/model/Device.js +++ b/lib/model/Device.js @@ -31,6 +31,7 @@ var Device = new Schema({ name: String, lazy: Array, active: Array, + staticAttributes: Array, service: String, subservice: String, timezone: String, diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index f72047377..da27bbf8a 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -55,7 +55,7 @@ function saveDeviceHandler(callback) { */ function storeDevice(newDevice, callback) { var deviceObj = new Device.model(), - attributeList = ['id', 'type', 'name', 'service', 'subservice', 'lazy', + attributeList = ['id', 'type', 'name', 'service', 'subservice', 'lazy', 'staticAttributes', 'active', 'registrationId', 'internalId', 'internalAttributes']; for (var i = 0; i < attributeList.length; i++) { diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index f611e354f..b100c1565 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -179,7 +179,7 @@ function registerDevice(deviceObj, callback) { if (config.types[deviceObj.type]) { logger.debug(context, 'Device type [%s] found', deviceObj.type); - var fields = ['service', 'subservice', 'lazy', 'internalAttributes', 'active']; + var fields = ['service', 'subservice', 'lazy', 'internalAttributes', 'active', 'staticAttributes']; for (var i = 0; i < fields.length; i++) { deviceData[fields[i]] = diff --git a/test/unit/mongodb-registry-test.js b/test/unit/mongodb-registry-test.js index 059a5679c..edf572301 100644 --- a/test/unit/mongodb-registry-test.js +++ b/test/unit/mongodb-registry-test.js @@ -54,6 +54,12 @@ var iotAgentLib = require('../../'), type: 'Hgmm' } ], + staticAttributes: [ + { + name: 'location', + type: 'Vector' + } + ], service: 'smartGondor', subservice: 'gardens', internalAttributes: { @@ -145,9 +151,12 @@ describe('MongoDB Device Registry', function() { should.exist(docs.length); docs.length.should.equal(1); should.exist(docs[0].internalAttributes); + should.exist(docs[0].staticAttributes); should.exist(docs[0].internalAttributes.customAttribute); should.exist(docs[0].active); docs[0].active.length.should.equal(1); + docs[0].staticAttributes.length.should.equal(1); + docs[0].staticAttributes[0].name.should.equal('location'); docs[0].active[0].name.should.equal('pressure'); docs[0].internalAttributes.customAttribute.should.equal('customValue'); done(); From b8f85b017f3b6f3fc744979d96e31b169ce1eb70 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 12:19:34 +0100 Subject: [PATCH 35/78] ADD Static attributes in update active parameter --- lib/services/ngsiService.js | 4 +++ test/unit/active-devices-test.js | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index b100c1565..792d31f1d 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -238,6 +238,10 @@ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, toke if (typeInformation.cbHost) { cbHost = typeInformation.cbHost; } + + if (typeInformation.staticAttributes) { + attributes = attributes.concat(typeInformation.staticAttributes); + } } options = { diff --git a/test/unit/active-devices-test.js b/test/unit/active-devices-test.js index c8263b687..5ec87525a 100644 --- a/test/unit/active-devices-test.js +++ b/test/unit/active-devices-test.js @@ -73,6 +73,23 @@ var iotAgentLib = require('../../'), type: 'percentage' } ] + }, + 'Motion': { + commands: [], + lazy: [], + staticAttributes: [ + { + "name": "location", + "type": "Vector", + "value": "(123,523)" + } + ], + active: [ + { + name: 'humidity', + type: 'percentage' + } + ] } }, service: 'smartGondor', @@ -202,4 +219,34 @@ describe('Active attributes test', function() { }); }); + describe('When an IoT Agent receives information for a type with static attributes', function() { + var newValues = [ + { + name: 'moving', + type: 'Boolean', + value: 'true' + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v1/updateContext', + utils.readExampleFile('./test/unit/contextRequests/updateContextStaticAttributes.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextResponses/updateContext1Success.json')); + + iotAgentLib.activate(iotAgentConfig, done); + }); + it('should decorate the entity with the static attributes', function(done) { + iotAgentLib.update('motion1', 'Motion', newValues, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); }); From e8b3e583a2059eef750e0cec6c92216d44af20ac Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 13:18:22 +0100 Subject: [PATCH 36/78] ADD Static attributes for context provider queries --- lib/services/contextServer.js | 35 +++++++++-- lib/services/deviceRegistryMemory.js | 2 +- test/unit/active-devices-test.js | 6 +- test/unit/lazy-devices-test.js | 93 ++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 8 deletions(-) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index c1ae0cc01..87200f496 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -23,8 +23,10 @@ 'use strict'; var async = require('async'), + apply = async.apply, logger = require('fiware-node-logger'), errors = require('../errors'), + ngsi = require('./ngsiService'), revalidator = require('revalidator'), context = { op: 'IoTAgentNGSI.ContextServer' @@ -208,18 +210,43 @@ function handleUpdate(req, res, next) { * each one of them a call to the user query handler function is made. */ function handleQuery(req, res, next) { + function addStaticAttributes(id, type, contextElement, callback) { + ngsi.getDeviceByName(id, function handleFindDevice(error, device) { + if (error) { + callback(error); + } else { + if (device.staticAttributes) { + contextElement.attributes = contextElement.attributes.concat(device.staticAttributes); + } + + callback(null, contextElement); + } + }); + } + if (queryHandler) { var queryRequests = []; logger.debug(context, 'Handling query from [%s]', req.get('host')); for (var i = 0; i < req.body.entities.length; i++) { - queryRequests.push( - async.apply( - queryHandler, + var executeQueryHandler = apply( + queryHandler, req.body.entities[i].id, req.body.entities[i].type, - req.body.attributes) + req.body.attributes + ), + executeAddStaticAttributes = apply( + addStaticAttributes, + req.body.entities[i].id, + req.body.entities[i].type + ); + + queryRequests.push( + async.apply(async.waterfall, [ + executeQueryHandler, + executeAddStaticAttributes + ]) ); } diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 62c0294ca..53988c763 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -92,7 +92,7 @@ function getByName(name, callback) { for (var i = 0; i < devices.length; i++) { if (devices[i].name === name) { - device = devices[i].name; + device = devices[i]; } } diff --git a/test/unit/active-devices-test.js b/test/unit/active-devices-test.js index 5ec87525a..a6f2898e5 100644 --- a/test/unit/active-devices-test.js +++ b/test/unit/active-devices-test.js @@ -79,9 +79,9 @@ var iotAgentLib = require('../../'), lazy: [], staticAttributes: [ { - "name": "location", - "type": "Vector", - "value": "(123,523)" + 'name': 'location', + 'type': 'Vector', + 'value': '(123,523)' } ], active: [ diff --git a/test/unit/lazy-devices-test.js b/test/unit/lazy-devices-test.js index 14cf84111..793d2ea8c 100644 --- a/test/unit/lazy-devices-test.js +++ b/test/unit/lazy-devices-test.js @@ -24,6 +24,8 @@ var iotAgentLib = require('../../'), utils = require('../tools/utils'), + async = require('async'), + apply = async.apply, should = require('should'), logger = require('fiware-node-logger'), nock = require('nock'), @@ -63,6 +65,23 @@ var iotAgentLib = require('../../'), ], active: [ ] + }, + 'Motion': { + commands: [], + lazy: [ + { + name: 'moving', + type: 'Boolean' + } + ], + staticAttributes: [ + { + 'name': 'location', + 'type': 'Vector', + 'value': '(123,523)' + } + ], + active: [] } }, service: 'smartGondor', @@ -74,6 +93,10 @@ var iotAgentLib = require('../../'), device1 = { id: 'light1', type: 'Light' + }, + device2 = { + id: 'motion1', + type: 'Motion' }; describe('IoT Agent Lazy Devices', function() { @@ -306,6 +329,7 @@ describe('IoT Agent Lazy Devices', function() { sensorData = [ { id: 'light1', + isPattern: false, type: 'Light', attributes: [ { @@ -350,6 +374,75 @@ describe('IoT Agent Lazy Devices', function() { }); }); + describe('When a context query arrives to the IoT Agent for a type with static attributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', + method: 'POST', + json: { + entities: [ + { + type: 'Motion', + isPattern: 'false', + id: 'motion1' + } + ], + attributes: [ + 'moving', + 'location' + ] + } + }, + sensorData = [ + { + id: 'motion1', + type: 'Motion', + attributes: [ + { + name: 'moving', + type: 'Boolean', + value: 'true' + } + ] + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent2.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device2) + ], done); + }); + + it('should return the information adding the static attributes', function(done) { + var expectedResponse = utils + .readExampleFile('./test/unit/contextProviderResponses/queryInformationStaticAttributesResponse.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, attributes, callback) { + id.should.equal('motion1'); + type.should.equal('Motion'); + attributes[0].should.equal('moving'); + attributes[1].should.equal('location'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); + describe('When a context query arrives to the IoT Agent with a payload that is not XML nor JSON', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', From 254c1a1c2477141a7dce361c782ae630dde77ecd Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 13:21:07 +0100 Subject: [PATCH 37/78] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 45ccb5349..96fd2f829 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,7 @@ Device groups contain the following attributes: * **lazy**: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. * **commands**: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. * **active**: list of active attributes of the device. For each attribute, its `name` and `type` must be provided. +* **staticAttributes**: this attributes will be added to all the entities of this group 'as is'. * **internalAttributes**: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. #### POST /iot/agent/:agentName/services @@ -584,6 +585,7 @@ The IoT Agent can be configured to expect certain kinds of devices, with preconf * **lazy**: list of lazy attributes of the device. For each attribute, its `name` and `type` must be provided. * **commands**: list of commands attributes of the device. For each attribute, its `name` and `type` must be provided. * **internalAttributes**: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. +* **staticAttributes**: this array of attributes will be added to every entity of this type 'as is'. * **trust**: trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). * **cbHost**: Context Broker host url. This option can be used to override the global CB configuration for specific types of devices. From 047470eb467c30fb89babaaa6b1bf42e53ff6a5e Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 13:23:11 +0100 Subject: [PATCH 38/78] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45ccb5349..91748f737 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ The device Object can have the following attributes: * subservice: name of the subservice associated with th device. * lazy: list of lazy attributes with their types. * active: list of active attributes with their types. +* staticAttributes: list of NGSI attributes to add to the device entity 'as is' in updates, queries and registrations. The device id and type are required fields for any registration. The rest of the attributes are optional, but, if they are not present in the function call arguments, the type must be registered in the configuration, so the service can infer their default values from the configured type. If an optional attribute is not given in the parameter list and there isn't a default configuration for the given type, a TypeNotFound error is raised. From dee52da9db2b663cf8e0cd6d93a54726cb96bec7 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 24 Feb 2015 13:32:41 +0100 Subject: [PATCH 39/78] FIX Confusion between static_attributes and lazy in the Provisoining API --- lib/services/deviceProvisioningServer.js | 6 ++++-- test/unit/device-provisioning-api_test.js | 8 +++++++- .../provisionAnotherDevice.json | 3 ++- .../provisionNewDevice.json | 14 ++++++++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index dfd7c42c6..c4a151e66 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -75,7 +75,8 @@ function handleProvision(req, res, next) { subservice: subservice, active: body['attributes'], staticAttributes: body['static_attributes'], - lazy: body['commands'], + lazy: body['lazy'], + commands: body['commands'], timezone: body['timezone'], internalAttributes: body['internal_attributes'], internalId: null @@ -121,7 +122,8 @@ function toProvisioningAPIFormat(device) { entity_type: device.type, timezone: device.timezone, attributes: device.active, - commands: device.lazy, + lazy: device.lazy, + static_attributes: device.staticAttributes, internal_attributes: device.internalAttributes }; } diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index a297147ec..edbbbf850 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -106,9 +106,15 @@ describe('Device provisioning API: Provision devices', function() { iotAgentLib.listDevices(function(error, results) { should.exist(results[0].timezone); results[0].timezone.should.equal('America/Santiago'); + should.exist(results[0].lazy); + results[0].lazy.length.should.equal(1); + results[0].lazy[0].name.should.equal('luminance'); + should.exist(results[0].staticAttributes); + results[0].commands.length.should.equal(1); + results[0].commands[0].name.should.equal('commandAttr'); should.exist(results[0].staticAttributes); results[0].staticAttributes.length.should.equal(1); - results[0].staticAttributes[0].name.should.equal('attr_name'); + results[0].staticAttributes[0].name.should.equal('hardcodedAttr'); should.exist(results[0].active); results[0].active.length.should.equal(1); results[0].active[0].name.should.equal('attr_name'); diff --git a/test/unit/deviceProvisioningRequests/provisionAnotherDevice.json b/test/unit/deviceProvisioningRequests/provisionAnotherDevice.json index d8f308ecc..eb75a81b9 100644 --- a/test/unit/deviceProvisioningRequests/provisionAnotherDevice.json +++ b/test/unit/deviceProvisioningRequests/provisionAnotherDevice.json @@ -17,12 +17,13 @@ "type": "string" } ], - "commands": [ + "lazy": [ { "name": "luminance", "type": "lumens" } ], + "commands": [], "internal_attributes": [ { "customField": "customValue" diff --git a/test/unit/deviceProvisioningRequests/provisionNewDevice.json b/test/unit/deviceProvisioningRequests/provisionNewDevice.json index e02bc8781..cd73d5d74 100644 --- a/test/unit/deviceProvisioningRequests/provisionNewDevice.json +++ b/test/unit/deviceProvisioningRequests/provisionNewDevice.json @@ -11,16 +11,22 @@ "type": "string" } ], + "lazy": [ + { + "name": "luminance", + "type": "lumens" + } + ], "static_attributes": [ { - "name": "attr_name", - "type": "string" + "name": "hardcodedAttr", + "type": "hardcodedType" } ], "commands": [ { - "name": "luminance", - "type": "lumens" + "name": "commandAttr", + "type": "commandType" } ], "internal_attributes": [ From 57999eeede5fc1c396479a9fa68a0aa59d5ea9d0 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 14:52:29 +0100 Subject: [PATCH 40/78] FIX Remove some mandatory constraints on device provisioning --- lib/services/deviceProvisioningServer.js | 2 +- test/unit/device-provisioning-api_test.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index c4a151e66..7a52161d0 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -88,7 +88,7 @@ function handleProvision(req, res, next) { async.waterfall([ apply(restUtils.checkMandatoryQueryParams, - ['name', 'entity_type', 'service', 'service_path', 'commands'], req.body), + ['name', 'entity_type'], req.body), apply(registerDevice, req.headers['fiware-service'], req.headers['fiware-servicepath']) ], handleProvisioningFinish); } diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index edbbbf850..0b7bf5e2c 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -154,7 +154,6 @@ describe('Device provisioning API: Provision devices', function() { should.exist(body); response.statusCode.should.equal(400); body.name.should.equal('MISSING_ATTRIBUTES'); - body.message.should.match(/.*service_path.*/); body.message.should.match(/.*entity_type.*/); done(); }); From f2ac5164d01c856e48c199ad5c9b04847da1fc76 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 14:53:18 +0100 Subject: [PATCH 41/78] ADD Changes to changelog --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 6346f7c11..5e5d1506a 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -2,3 +2,4 @@ - ADD Configuration API for dynamically creating configuration groups. - ADD Device Provisioning API and Device Configuration API commands in the testing clients. - ADD Pagination in the Device List operations. +- FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). \ No newline at end of file From bfa9058cef8474705f9b795144454c528482f3c3 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 15:48:22 +0100 Subject: [PATCH 42/78] FIX Associate the device listing to service and subservice --- README.md | 8 +++- lib/services/deviceProvisioningServer.js | 19 +++++--- lib/services/deviceRegistryMemory.js | 9 +++- lib/services/deviceRegistryMongoDB.js | 7 ++- lib/services/ngsiService.js | 6 ++- test/unit/device-provisioning-api_test.js | 10 ++-- test/unit/listProvisionedDevices-test.js | 58 ++++++++++++++++++----- 7 files changed, 86 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index e4e4a6ae0..177457465 100644 --- a/README.md +++ b/README.md @@ -157,11 +157,15 @@ can be invoked with an externally added deviceInformation object to overwrite th ##### iotagentLib.listDevices() ###### Signature ``` -function listDevices(callback) +function listDevices(service, subservice, limit, offset, callback) ``` ###### Description -Return a list of all the devices registered in the system. +Return a list of all the devices registered in the specified service and subservice. ###### Params +* service: service from where the devices will be retrieved. +* subservice: subservice from where the devices will be retrieved. +* limit: maximum number of results to retrieve (optional). +* offset: number of results to skip from the listing (optional). ##### iotagentLib.setDataUpdateHandler() ###### Signature diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index 7a52161d0..e99ac360b 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -97,13 +97,18 @@ function handleProvision(req, res, next) { * Express middleware that retrieves the complete set of provisioned devices (in JSON format). */ function handleListDevices(req, res, next) { - ngsi.listDevices(req.query.limit, req.query.offset, function handleListDevices(error, deviceList) { - if (error) { - next(error); - } else { - res.status(200).json(deviceList); - } - }); + ngsi.listDevices( + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + req.query.limit, + req.query.offset, + function handleListDevices(error, deviceList) { + if (error) { + next(error); + } else { + res.status(200).json(deviceList); + } + }); } /** diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 53988c763..8802021d7 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -59,15 +59,20 @@ function removeDevice(id, callback) { * Return the list of currently registered devices (via callback). This function can be invoked in two different ways: * with just one parameter (the callback) or with three parameters (including limit and offset). * + * @param {String} service Service for which the entries will be returned. + * @param {String} subservice Subservice for which the entries will be listed. * @param {Number} limit Maximum number of entries to return. * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(limit, offset, callback) { +function listDevices(service, subservice, limit, offset, callback) { var result = [], skipped = 0; for (var i in registeredDevices) { - if (registeredDevices.hasOwnProperty(i)) { + if (registeredDevices.hasOwnProperty(i) && + registeredDevices[i].service === service && + registeredDevices[i].subservice === subservice) { + if (offset && skipped < parseInt(offset, 10)) { skipped++; } else { diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index da27bbf8a..66bc284ef 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -95,8 +95,11 @@ function removeDevice(id, callback) { /** * Return the list of currently registered devices (via callback). */ -function listDevices(limit, offset, callback) { - var condition = {}, +function listDevices(service, subservice, limit, offset, callback) { + var condition = { + service: service, + subservice: subservice + }, query = Device.model.find(condition).sort(); if (limit) { diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 792d31f1d..32ce182db 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -396,16 +396,18 @@ function updateRegisterDevice(deviceObj, callback) { * Return a list of all the devices registered in the system. This function can be invoked in two different ways: * with just one parameter (the callback) or with three parameters (including limit and offset). * + * @param {String} service + * @param {String} subservice * @param {Number} limit Maximum number of entries to return. * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(limit, offset, callback) { +function listDevices(service, subservice, limit, offset, callback) { if (!callback) { callback = limit; } if (registry) { - registry.list(limit, offset, callback); + registry.list(service, subservice, limit, offset, callback); } else { logger.error(context, 'Tried to list devices before a registry was available'); callback(new errors.RegistryNotAvailable()); diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index 0b7bf5e2c..788e08b87 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -83,7 +83,7 @@ describe('Device provisioning API: Provision devices', function() { should.not.exist(error); response.statusCode.should.equal(200); - iotAgentLib.listDevices(function(error, results) { + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { results.length.should.equal(1); done(); }); @@ -92,7 +92,7 @@ describe('Device provisioning API: Provision devices', function() { it('should store the device with the provided entity id, name and type', function(done) { request(options, function(error, response, body) { response.statusCode.should.equal(200); - iotAgentLib.listDevices(function(error, results) { + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { results[0].id.should.equal('Light1'); results[0].name.should.equal('TheFirstLight'); results[0].type.should.equal('TheLightType'); @@ -103,7 +103,7 @@ describe('Device provisioning API: Provision devices', function() { it('should store the device with the per device information', function(done) { request(options, function(error, response, body) { response.statusCode.should.equal(200); - iotAgentLib.listDevices(function(error, results) { + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { should.exist(results[0].timezone); results[0].timezone.should.equal('America/Santiago'); should.exist(results[0].lazy); @@ -128,7 +128,7 @@ describe('Device provisioning API: Provision devices', function() { it('should store service and subservice info from the headers along with the device data', function(done) { request(options, function(error, response, body) { response.statusCode.should.equal(200); - iotAgentLib.listDevices(function(error, results) { + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { should.exist(results[0].service); results[0].service.should.equal('smartGondor'); should.exist(results[0].subservice); @@ -189,7 +189,7 @@ describe('Device provisioning API: Provision devices', function() { should.not.exist(error); response.statusCode.should.equal(200); - iotAgentLib.listDevices(function(error, results) { + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { results.length.should.equal(1); done(); }); diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index 813f4132a..486a82cd3 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -49,7 +49,8 @@ var iotAgentLib = require('../../'), describe('Device provisioning API: List provisioned devices', function() { var provisioning1Options, - provisioning2Options; + provisioning2Options, + provisioning3Options; beforeEach(function(done) { provisioning1Options = { @@ -74,21 +75,13 @@ describe('Device provisioning API: List provisioned devices', function() { iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') - .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', '/gardens') - .post('/NGSI9/registerContext', - utils.readExampleFile( - './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) + .post('/NGSI9/registerContext') .reply(200, utils.readExampleFile( './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); contextBrokerMock - .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', '/gardens') - .post('/NGSI9/registerContext', - utils.readExampleFile( - './test/unit/contextAvailabilityRequests/registerProvisionedDevice2.json')) + .post('/NGSI9/registerContext') .reply(200, utils.readExampleFile( './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); @@ -283,4 +276,47 @@ describe('Device provisioning API: List provisioned devices', function() { }); }); }); + + describe('When a listing request arrives and there are devices in other service and servicepath', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + }, + method: 'GET' + }; + + beforeEach(function (done) { + provisioning3Options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + headers: { + 'fiware-service': 'dumbMordor', + 'fiware-servicepath': '/gardens' + }, + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionYetAnotherDevice.json') + }; + + contextBrokerMock + .post('/NGSI9/registerContext') + .reply(200, + utils.readExampleFile( + './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); + + request(provisioning3Options, function (error) { + done(); + }); + }); + + it('should return just the ones in the selected service', function(done) { + request(options, function(error, response, body) { + var parsedBody = JSON.parse(body); + should.not.exist(error); + response.statusCode.should.equal(200); + parsedBody.length.should.equal(2); + done(); + }); + }); + }); }); From 13190a21afb434ac3976e219348e23f4711552b1 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 15:49:55 +0100 Subject: [PATCH 43/78] FIX Linter errors and add changes to changelog --- CHANGES_NEXT_RELEASE | 3 ++- test/unit/listProvisionedDevices-test.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 5e5d1506a..7bacebc18 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -2,4 +2,5 @@ - ADD Configuration API for dynamically creating configuration groups. - ADD Device Provisioning API and Device Configuration API commands in the testing clients. - ADD Pagination in the Device List operations. -- FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). \ No newline at end of file +- FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). +- FIX List devices based on the service and servicepath (#45); \ No newline at end of file diff --git a/test/unit/listProvisionedDevices-test.js b/test/unit/listProvisionedDevices-test.js index 486a82cd3..f0796d950 100644 --- a/test/unit/listProvisionedDevices-test.js +++ b/test/unit/listProvisionedDevices-test.js @@ -287,7 +287,7 @@ describe('Device provisioning API: List provisioned devices', function() { method: 'GET' }; - beforeEach(function (done) { + beforeEach(function(done) { provisioning3Options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', @@ -304,7 +304,7 @@ describe('Device provisioning API: List provisioned devices', function() { utils.readExampleFile( './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); - request(provisioning3Options, function (error) { + request(provisioning3Options, function(error) { done(); }); }); From 35334889b05a11459a7627bbeebfee84d116f996 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 17:09:58 +0100 Subject: [PATCH 44/78] FIX Refactor types in registration --- config.js | 3 +- lib/services/ngsiService.js | 24 +++++------ test/unit/device-provisioning-api_test.js | 51 ++++++++++++++++++++++- test/unit/device-registration_test.js | 33 --------------- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/config.js b/config.js index 3422c253f..78a61fe31 100644 --- a/config.js +++ b/config.js @@ -66,7 +66,8 @@ var config = { service: 'smartGondor', subservice: '/gardens', providerUrl: 'http://192.168.56.1:4041', - deviceRegistrationDuration: 'P1M' + deviceRegistrationDuration: 'P1M', + defaultType: 'Thing' }; module.exports = config; diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 32ce182db..0e5c91a30 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -166,7 +166,9 @@ function processContextRegistration(deviceData, body, callback) { * @param {Object} deviceObj Object with all the device information (mandatory). */ function registerDevice(deviceObj, callback) { - var deviceData = _.clone(deviceObj); + var deviceData = _.clone(deviceObj), + fields = ['service', 'subservice', 'lazy', 'internalAttributes', 'active', 'staticAttributes', 'commands'], + defaults = [null, null, [], [], [], []]; logger.debug(context, 'Registering device into NGSI Service:\n%s', JSON.stringify(deviceData, null, 4)); @@ -175,20 +177,16 @@ function registerDevice(deviceObj, callback) { deviceData.name = deviceData.id; } - if (!deviceObj.service || !deviceObj.subservice || !deviceObj.lazy) { - if (config.types[deviceObj.type]) { - logger.debug(context, 'Device type [%s] found', deviceObj.type); - - var fields = ['service', 'subservice', 'lazy', 'internalAttributes', 'active', 'staticAttributes']; + if (!deviceData.type) { + deviceData.type = config.defaultType; + } - for (var i = 0; i < fields.length; i++) { - deviceData[fields[i]] = - (deviceObj[fields[i]]) ? deviceData[fields[i]] : config.types[deviceObj.type][fields[i]]; - } + for (var i = 0; i < fields.length; i++) { + if (config.types[deviceData.type] && config.types[deviceObj.type][fields[i]]) { + deviceData[fields[i]] = + (deviceData[fields[i]]) ? deviceData[fields[i]] : config.types[deviceObj.type][fields[i]]; } else { - logger.debug(context, 'Device type not found [%s] for device [%s]', deviceObj.type, deviceData.name); - callback(new errors.TypeNotFound(deviceObj.id, deviceObj.type)); - return; + deviceData[fields[i]] = (deviceData[fields[i]]) ? deviceData[fields[i]] : defaults[i]; } } diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index 788e08b87..843b29356 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -24,6 +24,7 @@ var iotAgentLib = require('../../'), utils = require('../tools/utils'), + should = require('should'), nock = require('nock'), request = require('request'), @@ -59,7 +60,7 @@ describe('Device provisioning API: Provision devices', function() { utils.readExampleFile( './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); - done(); + iotAgentLib.clearAll(done); }); }); @@ -138,6 +139,54 @@ describe('Device provisioning API: Provision devices', function() { }); }); }); + describe('When a device provisioning request with the minimum required data arrives to the IoT Agent', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionMinimumDevice.json'), + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + } + }; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/NGSI9/registerContext') + .reply(200, + utils.readExampleFile( + './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); + + done(); + }); + + it('should add the device to the devices list', function(done) { + request(options, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { + results.length.should.equal(1); + done(); + }); + }); + }); + it('should store the device with the provided entity id, name and type', function(done) { + request(options, function(error, response, body) { + response.statusCode.should.equal(200); + iotAgentLib.listDevices('smartGondor', '/gardens', function(error, results) { + results[0].id.should.equal('MicroLight1'); + results[0].name.should.equal('FirstMicroLight'); + results[0].type.should.equal('MicroLights'); + done(); + }); + }); + }); + }); describe('When a device provisioning request with missing data arrives to the IoT Agent', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', diff --git a/test/unit/device-registration_test.js b/test/unit/device-registration_test.js index 413c0999a..c76cf146d 100644 --- a/test/unit/device-registration_test.js +++ b/test/unit/device-registration_test.js @@ -253,37 +253,4 @@ describe('IoT Agent Device Registration', function() { }); }); }); - - describe('When a device is registered in the Context Broker and its type is not configured', function() { - var unexistentDevice = { - id: device1.id, - type: 'UnexistentType' - }; - - beforeEach(function(done) { - nock.cleanAll(); - - contextBrokerMock = nock('http://10.11.128.16:1026') - .matchHeader('fiware-service', 'smartGondor') - .matchHeader('fiware-servicepath', 'gardens') - .post('/NGSI9/registerContext', - utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent1.json')) - .reply(500, - utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Failed.json')); - - iotAgentLib.activate(iotAgentConfig, function(error) { - done(); - }); - }); - - it('should raise a TYPE_NOT_FOUND error', function(done) { - iotAgentLib.register(unexistentDevice, function(error) { - should.exist(error); - should.exist(error.name); - error.name.should.equal('TYPE_NOT_FOUND'); - - done(); - }); - }); - }); }); From 729d60fc8bda252e1030cc691c2c9c6f48596293 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 4 Mar 2015 17:10:30 +0100 Subject: [PATCH 45/78] ADD Changes to changelog --- CHANGES_NEXT_RELEASE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 7bacebc18..8225dbce3 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -3,4 +3,5 @@ - ADD Device Provisioning API and Device Configuration API commands in the testing clients. - ADD Pagination in the Device List operations. - FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). -- FIX List devices based on the service and servicepath (#45); \ No newline at end of file +- FIX List devices based on the service and servicepath (#45). +- FIX Lazy attributes mandatory in Device Provisioning (#51). \ No newline at end of file From 68870b8cea10b188a7bb7701a7300a0483849bd8 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 17:34:12 +0100 Subject: [PATCH 46/78] ADD Configuration handler management --- CHANGES_NEXT_RELEASE | 3 ++- lib/fiware-iotagent-lib.js | 1 + lib/services/contextServer.js | 1 - .../deviceGroupAdministrationServer.js | 26 ++++++++++++++++++- lib/services/groupService.js | 1 + lib/services/northboundServer.js | 1 + test/unit/device-group-api-test.js | 17 ++++++++++++ 7 files changed, 47 insertions(+), 3 deletions(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 8225dbce3..d99ab0ed5 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -4,4 +4,5 @@ - ADD Pagination in the Device List operations. - FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). - FIX List devices based on the service and servicepath (#45). -- FIX Lazy attributes mandatory in Device Provisioning (#51). \ No newline at end of file +- FIX Lazy attributes mandatory in Device Provisioning (#51). +- FIX Should allow to provision devices with unconfigured type (#50) \ No newline at end of file diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index 523b62655..56244c9aa 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -95,3 +95,4 @@ exports.getDevice = ngsi.getDevice; exports.getDeviceByName = ngsi.getDeviceByName; exports.setDataUpdateHandler = contextServer.setUpdateHandler; exports.setDataQueryHandler = contextServer.setQueryHandler; +exports.setConfigurationHandler = contextServer.setConfigurationHandler; diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index 87200f496..a8b003957 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -302,7 +302,6 @@ function setUpdateHandler(newHandler) { * @param {Function} newHandler User handler for query requests */ - function setQueryHandler(newHandler) { queryHandler = newHandler; } diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index f7c82cc59..a05931ab3 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -26,8 +26,11 @@ var logger = require('fiware-node-logger'), errors = require('../errors'), restUtils = require('./restUtils'), groupService = require('./groupService'), + async = require('async'), + apply = async.apply, revalidator = require('revalidator'), templateGroup = require('../templates/deviceGroup.json'), + configurationHandler, context = { op: 'IoTAgentNGSI.GroupServer' }, @@ -56,6 +59,19 @@ function checkBody(template) { }; } +/** + * Apply the handler for configuration updates if there is any. + * + * @param {Object} newConfiguration New configuration that is being loaded. + */ +function applyConfigurationHandler(newConfiguration, callback) { + if (configurationHandler && newConfiguration && newConfiguration.services) { + async.map(newConfiguration.services, configurationHandler, callback); + } else { + callback(); + } +} + /** * Handle the device group creation requests, adding the header information to the device group body. * @@ -69,7 +85,10 @@ function handleCreateDeviceGroup(req, res, next) { req.body.services[i].subservice = req.headers['fiware-servicepath']; } - groupService.create(req.body, function(error) { + async.series([ + apply(groupService.create, req.body), + apply(applyConfigurationHandler, req.body) + ], function(error) { if (error) { next(error); } else { @@ -161,4 +180,9 @@ function loadContextRoutes(router, name) { restUtils.checkHeaders(mandatoryHeaders), handleDeleteDeviceGroups); } +function setConfigurationHandler(newHandler) { + configurationHandler = newHandler; +} + exports.loadContextRoutes = loadContextRoutes; +exports.setConfigurationHandler = setConfigurationHandler; diff --git a/lib/services/groupService.js b/lib/services/groupService.js index e0dc9b423..b83e87012 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -47,6 +47,7 @@ function createGroup(groupSet, callback) { for (var i = 0; i < groupSet.services.length; i++) { insertions.push(async.apply(registry.create, groupSet.services[i])); + insertions.push insertedGroups.push(groupSet.services[i]); } diff --git a/lib/services/northboundServer.js b/lib/services/northboundServer.js index 5a844eee0..d74cc4e63 100644 --- a/lib/services/northboundServer.js +++ b/lib/services/northboundServer.js @@ -113,6 +113,7 @@ function stop(callback) { exports.setUpdateHandler = contextServer.setUpdateHandler; exports.setQueryHandler = contextServer.setQueryHandler; +exports.setConfigurationHandler = groupProvisioning.setConfigurationHandler; exports.start = start; exports.stop = stop; diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 51e5106db..dacc25263 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -190,6 +190,23 @@ describe('Device Group Configuration API', function() { done(); }); }); + it('should call the configuration creation handler', function(done) { + var handlerCalled = false; + + iotAgentLib.setConfigurationHandler(function (newConfiguration, callback) { + should.exist(newConfiguration); + should.exist(callback); + newConfiguration.apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732'); + newConfiguration.trust.should.equal('8970A9078A803H3BL98PINEQRW8342HBAMS'); + handlerCalled = true; + callback(); + }); + + request(optionsCreation, function(error, response, body) { + handlerCalled.should.equal(true); + done(); + }); + }); }); describe('When a creation request arrives without the fiware-service header', function() { beforeEach(function() { From 8965125b8ea26715d1603b7be2796a6bbe8ed1d3 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 17:59:36 +0100 Subject: [PATCH 47/78] ADD Configuration handler invocation for updates also --- .../deviceGroupAdministrationServer.js | 15 ++++++++++++--- test/unit/device-group-api-test.js | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index a05931ab3..db3b8f37a 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -65,8 +65,12 @@ function checkBody(template) { * @param {Object} newConfiguration New configuration that is being loaded. */ function applyConfigurationHandler(newConfiguration, callback) { - if (configurationHandler && newConfiguration && newConfiguration.services) { - async.map(newConfiguration.services, configurationHandler, callback); + if (configurationHandler && newConfiguration) { + if (newConfiguration.services) { + async.map(newConfiguration.services, configurationHandler, callback); + } else { + configurationHandler(newConfiguration, callback); + } } else { callback(); } @@ -137,13 +141,18 @@ function handleListDeviceGroups(req, res, next) { * @param {Function} next Invokes the next middleware in the chain. */ function handleModifyDeviceGroups(req, res, next) { - groupService.update(req.headers['fiware-service'], req.headers['fiware-servicepath'], req.body, function(error) { + async.series([ + apply(groupService.update, req.headers['fiware-service'], req.headers['fiware-servicepath'], req.body), + apply(applyConfigurationHandler, req.body) + ], function(error) { if (error) { next(error); } else { res.status(200).send({}); } }); + + } /** diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index dacc25263..a651e52de 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -151,6 +151,8 @@ describe('Device Group Configuration API', function() { }); afterEach(function(done) { + iotAgentLib.setConfigurationHandler(); + iotAgentLib.deactivate(function() { groupRegistryMemory.clear(done); }); @@ -341,6 +343,23 @@ describe('Device Group Configuration API', function() { done(); }); }); + it('should call the configuration creation handler', function(done) { + var handlerCalled = false; + + iotAgentLib.setConfigurationHandler(function (newConfiguration, callback) { + should.exist(newConfiguration); + should.exist(callback); + newConfiguration.cbHost.should.equal('http://anotherUnexistentHost:1026'); + newConfiguration.trust.should.equal('8970A9078A803H3BL98PINEQRW8342HBAMS'); + handlerCalled = true; + callback(); + }); + + request(optionsUpdate, function(error, response, body) { + handlerCalled.should.equal(true); + done(); + }); + }); }); describe('When a device group update request arrives without the mandatory headers', function() { From b193f877cb428255fa672c0c124e694373cdb3bb Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 18:03:35 +0100 Subject: [PATCH 48/78] FIX Linter errors --- lib/services/groupService.js | 1 - test/unit/device-group-api-test.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/services/groupService.js b/lib/services/groupService.js index b83e87012..e0dc9b423 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -47,7 +47,6 @@ function createGroup(groupSet, callback) { for (var i = 0; i < groupSet.services.length; i++) { insertions.push(async.apply(registry.create, groupSet.services[i])); - insertions.push insertedGroups.push(groupSet.services[i]); } diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index a651e52de..327804cb0 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -195,7 +195,7 @@ describe('Device Group Configuration API', function() { it('should call the configuration creation handler', function(done) { var handlerCalled = false; - iotAgentLib.setConfigurationHandler(function (newConfiguration, callback) { + iotAgentLib.setConfigurationHandler(function(newConfiguration, callback) { should.exist(newConfiguration); should.exist(callback); newConfiguration.apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732'); @@ -346,7 +346,7 @@ describe('Device Group Configuration API', function() { it('should call the configuration creation handler', function(done) { var handlerCalled = false; - iotAgentLib.setConfigurationHandler(function (newConfiguration, callback) { + iotAgentLib.setConfigurationHandler(function(newConfiguration, callback) { should.exist(newConfiguration); should.exist(callback); newConfiguration.cbHost.should.equal('http://anotherUnexistentHost:1026'); From 8f180455306540e592809a4394025f636203e9c2 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 18:13:58 +0100 Subject: [PATCH 49/78] Update README.md --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 177457465..07ce57f6a 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,23 @@ The callback must be invoked with the updated Context Element, using the informa In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each entity, and all the results will be combined into a single response. ###### Params * newHandler: User handler for query requests. - + +##### iotagentLib.setConfigurationHandler() +###### Signature +``` +function setConfigurationHandler(newHandler) +``` +###### Description +Sets the new user handler for the configuration updates. This handler will be called every time a new configuration is created or an old configuration is updated. + +The handler must adhere to the following signature: +``` +function(newConfiguration, callback) +``` +The `newConfiguration` parameter will contain the newly created configuration. The handler is expected to call its callback with no parameters (this handler should only be used for reconfiguration purposes of the IOT Agent). + +For the cases of multiple updates (a single Device Configuration POST that will create several device groups), the handler will be called once for each of the configurations (both in the case of the creatinos and the updates). + ##### iotagentLib.listDevices() ###### Signature ``` From 297c19c2f6ad0c626420c66d5a223d71613e9efc Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 18:14:41 +0100 Subject: [PATCH 50/78] ADD Changes to the change log --- CHANGES_NEXT_RELEASE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index d99ab0ed5..fb678a2d9 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -5,4 +5,5 @@ - FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46). - FIX List devices based on the service and servicepath (#45). - FIX Lazy attributes mandatory in Device Provisioning (#51). -- FIX Should allow to provision devices with unconfigured type (#50) \ No newline at end of file +- FIX Should allow to provision devices with unconfigured type (#50) +- ADD Handler for configuration changes from the Configuration API. \ No newline at end of file From c629ca04d69a92ccfc74a8a81f62523e51a4ca42 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 11 Mar 2015 23:39:03 +0100 Subject: [PATCH 51/78] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 07ce57f6a..09610c30d 100644 --- a/README.md +++ b/README.md @@ -520,7 +520,7 @@ Device groups contain the following attributes: * **staticAttributes**: this attributes will be added to all the entities of this group 'as is'. * **internalAttributes**: optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. -#### POST /iot/agent/:agentName/services +#### POST /iot/agents/:agentName/services Creates a set of device groups for the given service and service path. The service and subservice information will taken from the headers, overwritting any preexisting values. Body params: @@ -565,7 +565,7 @@ Returns: * 400 WRONG_SYNTAX if the body doesn't comply with the schema. * 500 SERVER ERROR if there was any error not contemplated above. -#### GET /iot/agent/:agentName/services +#### GET /iot/agents/:agentName/services Retrieves device groups from the database. If the servicepath header has de wildcard expression, '/*', all the subservices for the service are returned. The specific subservice parameters are returned in any other case. Returns: @@ -573,7 +573,7 @@ Returns: * 400 MISSING_HEADERS if any of the mandatory headers is not present. * 500 SERVER ERROR if there was any error not contemplated above. -#### PUT /iot/agent/:agentName/services +#### PUT /iot/agents/:agentName/services Modifies the information for a device group configuration, identified by its service and subservice. Takes a device group body as the payload. The body does not have to be complete: for incomplete bodies, just the existing attributes will be updated E.g.: @@ -589,7 +589,7 @@ Returns: * 400 MISSING_HEADERS if any of the mandatory headers is not present. * 500 SERVER ERROR if there was any error not contemplated above. -#### DELETE /iot/agent/:agentName/services +#### DELETE /iot/agents/:agentName/services Removes a device group configuration from the DB, specified by the service and subservice headers. Returns: From 8eab2110f453ac503055a18db4041f0b7b4bba6d Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 09:55:17 +0100 Subject: [PATCH 52/78] ADD Constraint to forbid multiple devices with the same Device ID --- lib/errors.js | 5 +++ lib/services/deviceRegistryMemory.js | 12 ++++-- .../registerIoTAgent2.json | 22 ++++++++++ ...ryInformationStaticAttributesResponse.json | 27 +++++++++++++ .../updateContextStaticAttributes.json | 22 ++++++++++ test/unit/device-provisioning-api_test.js | 40 +++++++++++++++++++ test/unit/device-registration_test.js | 8 ++-- .../provisionMinimumDevice.json | 11 +++++ .../provisionYetAnotherDevice.json | 30 ++++++++++++++ 9 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 test/unit/contextAvailabilityRequests/registerIoTAgent2.json create mode 100644 test/unit/contextProviderResponses/queryInformationStaticAttributesResponse.json create mode 100644 test/unit/contextRequests/updateContextStaticAttributes.json create mode 100644 test/unit/deviceProvisioningRequests/provisionMinimumDevice.json create mode 100644 test/unit/deviceProvisioningRequests/provisionYetAnotherDevice.json diff --git a/lib/errors.js b/lib/errors.js index ec4722f24..e3c41ba26 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -71,6 +71,11 @@ module.exports = { this.message = 'No device was found with id:' + id; this.code = 404; }, + DuplicateDeviceId: function(id) { + this.name = 'DUPLICATE_DEVICE_ID'; + this.message = 'A device with the same pair (Service, DeviceId) was found:' + id; + this.code = 400; + }, SecurityInformationMissing: function(type) { this.name = 'SECURITY_INFORMATION_MISSING'; this.message = 'Some security information was missing for device type:' + type; diff --git a/lib/services/deviceRegistryMemory.js b/lib/services/deviceRegistryMemory.js index 8802021d7..e7a20e520 100644 --- a/lib/services/deviceRegistryMemory.js +++ b/lib/services/deviceRegistryMemory.js @@ -35,11 +35,15 @@ var registeredDevices = {}, * @param {Object} newDevice Device object to be stored */ function storeDevice(newDevice, callback) { - registeredDevices[newDevice.id] = newDevice; - registeredDevices[newDevice.id].creationDate = Date.now(); + if (registeredDevices[newDevice.id]) { + callback(new errors.DuplicateDeviceId(newDevice.id)); + } else { + registeredDevices[newDevice.id] = newDevice; + registeredDevices[newDevice.id].creationDate = Date.now(); - logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type); - callback(null); + logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type); + callback(null); + } } /** diff --git a/test/unit/contextAvailabilityRequests/registerIoTAgent2.json b/test/unit/contextAvailabilityRequests/registerIoTAgent2.json new file mode 100644 index 000000000..1e91c9771 --- /dev/null +++ b/test/unit/contextAvailabilityRequests/registerIoTAgent2.json @@ -0,0 +1,22 @@ +{ + "contextRegistrations": [ + { + "entities": [ + { + "type": "Motion", + "isPattern": "false", + "id": "motion1" + } + ], + "attributes": [ + { + "name": "moving", + "type": "Boolean", + "isDomain": "false" + } + ], + "providingApplication": "http://smartGondor.com" + } + ], + "duration": "P1M" +} \ No newline at end of file diff --git a/test/unit/contextProviderResponses/queryInformationStaticAttributesResponse.json b/test/unit/contextProviderResponses/queryInformationStaticAttributesResponse.json new file mode 100644 index 000000000..d28811036 --- /dev/null +++ b/test/unit/contextProviderResponses/queryInformationStaticAttributesResponse.json @@ -0,0 +1,27 @@ +{ + "contextResponses": [ + { + "contextElement": { + "attributes": [ + { + "name": "moving", + "type": "Boolean", + "value": "true" + }, + { + "name": "location", + "type": "Vector", + "value": "(123,523)" + } + ], + "id": "motion1", + "isPattern": false, + "type": "Motion" + }, + "statusCode": { + "code": 200, + "reasonPhrase": "OK" + } + } + ] +} \ No newline at end of file diff --git a/test/unit/contextRequests/updateContextStaticAttributes.json b/test/unit/contextRequests/updateContextStaticAttributes.json new file mode 100644 index 000000000..180e9eff9 --- /dev/null +++ b/test/unit/contextRequests/updateContextStaticAttributes.json @@ -0,0 +1,22 @@ +{ + "contextElements": [ + { + "type": "Motion", + "isPattern": "false", + "id": "motion1", + "attributes": [ + { + "name": "moving", + "type": "Boolean", + "value": "true" + }, + { + "name": "location", + "type": "Vector", + "value": "(123,523)" + } + ] + } + ], + "updateAction": "APPEND" +} \ No newline at end of file diff --git a/test/unit/device-provisioning-api_test.js b/test/unit/device-provisioning-api_test.js index 843b29356..ba692716c 100644 --- a/test/unit/device-provisioning-api_test.js +++ b/test/unit/device-provisioning-api_test.js @@ -49,6 +49,8 @@ var iotAgentLib = require('../../'), describe('Device provisioning API: Provision devices', function() { beforeEach(function(done) { + nock.cleanAll(); + iotAgentLib.activate(iotAgentConfig, function() { contextBrokerMock = nock('http://10.11.128.16:1026') .matchHeader('fiware-service', 'smartGondor') @@ -65,6 +67,8 @@ describe('Device provisioning API: Provision devices', function() { }); afterEach(function(done) { + nock.cleanAll(); + iotAgentLib.deactivate(done); }); @@ -208,6 +212,42 @@ describe('Device provisioning API: Provision devices', function() { }); }); }); + describe('When two device provisioning requests with the same service and Device ID arrive', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', + method: 'POST', + json: utils.readExampleFile('./test/unit/deviceProvisioningRequests/provisionNewDevice.json'), + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': '/gardens' + } + }; + + beforeEach(function(done) { + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile( + './test/unit/contextAvailabilityRequests/registerProvisionedDevice.json')) + .reply(200, + utils.readExampleFile( + './test/unit/contextAvailabilityResponses/registerProvisionedDeviceSuccess.json')); + + done(); + }); + + it('should raise a DUPLICATE_ID error, indicating the ID was already in use', function(done) { + request(options, function(error, response, body) { + request(options, function(error, response, body) { + should.exist(body); + response.statusCode.should.equal(400); + body.name.should.equal('DUPLICATE_DEVICE_ID'); + done(); + }); + }); + }); + }); describe('When a device provisioning request is missing the "name" attribute', function() { it('should raise a MISSING_ATTRIBUTES error, indicating the missing attributes'); }); diff --git a/test/unit/device-registration_test.js b/test/unit/device-registration_test.js index c76cf146d..1d8437922 100644 --- a/test/unit/device-registration_test.js +++ b/test/unit/device-registration_test.js @@ -106,7 +106,7 @@ describe('IoT Agent Device Registration', function() { utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); iotAgentLib.activate(iotAgentConfig, function(error) { - done(); + iotAgentLib.clearAll(done); }); }); @@ -132,7 +132,7 @@ describe('IoT Agent Device Registration', function() { utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Failed.json')); iotAgentLib.activate(iotAgentConfig, function(error) { - done(); + iotAgentLib.clearAll(done); }); }); @@ -159,7 +159,7 @@ describe('IoT Agent Device Registration', function() { utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Failed.json')); iotAgentLib.activate(iotAgentConfig, function(error) { - done(); + iotAgentLib.clearAll(done); }); }); @@ -198,6 +198,7 @@ describe('IoT Agent Device Registration', function() { iotAgentLib.activate(iotAgentConfig, function(error) { async.series([ + async.apply(iotAgentLib.clearAll), async.apply(iotAgentLib.register, device1), async.apply(iotAgentLib.register, device2) ], done); @@ -236,6 +237,7 @@ describe('IoT Agent Device Registration', function() { iotAgentLib.activate(iotAgentConfig, function(error) { async.series([ + async.apply(iotAgentLib.clearAll), async.apply(iotAgentLib.register, device1), async.apply(iotAgentLib.register, device2) ], done); diff --git a/test/unit/deviceProvisioningRequests/provisionMinimumDevice.json b/test/unit/deviceProvisioningRequests/provisionMinimumDevice.json new file mode 100644 index 000000000..abc66b39d --- /dev/null +++ b/test/unit/deviceProvisioningRequests/provisionMinimumDevice.json @@ -0,0 +1,11 @@ +{ + "name": "MicroLight1", + "entity_name": "FirstMicroLight", + "entity_type": "MicroLights", + "attributes": [ + { + "name": "attr_name", + "type": "string" + } + ] +} diff --git a/test/unit/deviceProvisioningRequests/provisionYetAnotherDevice.json b/test/unit/deviceProvisioningRequests/provisionYetAnotherDevice.json new file mode 100644 index 000000000..5bfda345d --- /dev/null +++ b/test/unit/deviceProvisioningRequests/provisionYetAnotherDevice.json @@ -0,0 +1,30 @@ +{ + "name": "Light3", + "entity_name": "TheSecondLight", + "entity_type": "TheLightType", + "timezone": "America/Santiago", + "attributes": [ + { + "name": "attr_name", + "type": "string" + } + ], + "static_attributes": [ + { + "name": "attr_name", + "type": "string" + } + ], + "lazy": [ + { + "name": "luminance", + "type": "lumens" + } + ], + "commands": [], + "internal_attributes": [ + { + "customField": "customValue" + } + ] +} From caa72cf6b3f21454ec459ade5a3019bcdeb58164 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 10:02:54 +0100 Subject: [PATCH 53/78] ADD Unique device ID constraint to MongoDB registry --- lib/model/Device.js | 2 +- lib/services/deviceRegistryMongoDB.js | 16 +++++++++++- test/unit/mongodb-registry-test.js | 36 +++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/model/Device.js b/lib/model/Device.js index 70ca630a6..751441882 100644 --- a/lib/model/Device.js +++ b/lib/model/Device.js @@ -26,7 +26,7 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema; var Device = new Schema({ - id: String, + id: {type:String, unique:true}, type: String, name: String, lazy: Array, diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index 66bc284ef..d76f923ea 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -64,7 +64,21 @@ function storeDevice(newDevice, callback) { logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type); - deviceObj.save(saveDeviceHandler(callback)); + deviceObj.save(function saveHandler(error, deviceDAO) { + if (error) { + if (error.code === 11000) { + logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error); + + callback(new errors.DuplicateDeviceId(newDevice.id)); + } else { + logger.debug(context, 'Error storing device information: %s', error); + + callback(new errors.InternalDbError(error)); + } + } else { + callback(null, deviceDAO.toObject()); + } + }); } /** diff --git a/test/unit/mongodb-registry-test.js b/test/unit/mongodb-registry-test.js index edf572301..0680ae0be 100644 --- a/test/unit/mongodb-registry-test.js +++ b/test/unit/mongodb-registry-test.js @@ -165,6 +165,42 @@ describe('MongoDB Device Registry', function() { }); }); + describe('When a device with the same Device ID tries to register to the IOT Agent', function() { + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); + + iotAgentLib.activate(iotAgentConfig, function(error) { + done(); + }); + }); + + it('should be registered in mongodb with all its attributes', function(done) { + iotAgentLib.register(device1, function(error) { + iotAgentLib.register(device1, function(error) { + should.exist(error); + error.name.should.equal('DUPLICATE_DEVICE_ID'); + done(); + }); + }); + }); + }); + describe('When a device is removed from the IoT Agent', function() { beforeEach(function(done) { var expectedPayload3 = utils From 632acd9fac6c6fb6f1f42bd2c73f28a2ea6fdae3 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 10:07:55 +0100 Subject: [PATCH 54/78] FIX Linter errors --- lib/model/Device.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model/Device.js b/lib/model/Device.js index 751441882..c1d9fe0f4 100644 --- a/lib/model/Device.js +++ b/lib/model/Device.js @@ -26,7 +26,7 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema; var Device = new Schema({ - id: {type:String, unique:true}, + id: { type: String, unique: true }, type: String, name: String, lazy: Array, From e861b4b48e0f97e43d2c703058b0b38f14687146 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 10:15:30 +0100 Subject: [PATCH 55/78] ADD Changes to changelog --- CHANGES_NEXT_RELEASE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index fb678a2d9..88529a165 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -6,4 +6,5 @@ - FIX List devices based on the service and servicepath (#45). - FIX Lazy attributes mandatory in Device Provisioning (#51). - FIX Should allow to provision devices with unconfigured type (#50) -- ADD Handler for configuration changes from the Configuration API. \ No newline at end of file +- ADD Handler for configuration changes from the Configuration API. +- ADD Constraint to forbid multiple devices with the same ID (#48). \ No newline at end of file From e436748ea67cac685aa27eee549c7dbc9e9a0d16 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 19:03:27 +0100 Subject: [PATCH 56/78] ADD Constraint for resource and apikey in groups --- lib/errors.js | 5 ++++ lib/services/groupRegistryMemory.js | 31 ++++++++++++++++++------ test/unit/device-group-api-test.js | 37 +++++++++++++++++++++++++---- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index e3c41ba26..552f2edea 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -76,6 +76,11 @@ module.exports = { this.message = 'A device with the same pair (Service, DeviceId) was found:' + id; this.code = 400; }, + DuplicateGroup: function(res, key) { + this.name = 'DUPLICATE_GROUP'; + this.message = 'A device configuration already exists for resource ' + res + ' and API Key ' + key; + this.code = 400; + }, SecurityInformationMissing: function(type) { this.name = 'SECURITY_INFORMATION_MISSING'; this.message = 'Some security information was missing for device type:' + type; diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index 1de8dcaca..b764088bf 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -31,18 +31,35 @@ var registeredGroups = {}, }, groupIds = 1; +function exists(group) { + var keys = _.keys(registeredGroups); + + for (var i in keys) { + if (registeredGroups[keys[i]].apikey === group.apikey && + registeredGroups[keys[i]].resource === group.resource) { + return true; + } + } + + return false; +} + function createGroup(group, callback) { - var storeGroup = _.clone(group); + if (exists(group)) { + callback(new errors.DuplicateGroup(group.resource, group.apikey)) + } else { + var storeGroup = _.clone(group); - storeGroup.id = groupIds++; + storeGroup.id = groupIds++; - registeredGroups[storeGroup.id] = storeGroup; - registeredGroups[storeGroup.id].creationDate = Date.now(); + registeredGroups[storeGroup.id] = storeGroup; + registeredGroups[storeGroup.id].creationDate = Date.now(); - logger.debug(context, 'Storing device group for service [%s] and subservice [%s]', - storeGroup.id, storeGroup.service, storeGroup.subservice); + logger.debug(context, 'Storing device group for service [%s] and subservice [%s]', + storeGroup.id, storeGroup.service, storeGroup.subservice); - callback(null); + callback(null); + } } function listGroups(callback) { diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 327804cb0..273473861 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -23,6 +23,7 @@ 'use strict'; var iotAgentLib = require('../../'), + _ = require('underscore'), async = require('async'), nock = require('nock'), utils = require('../tools/utils'), @@ -210,6 +211,18 @@ describe('Device Group Configuration API', function() { }); }); }); + describe('When a new creation request arrives for a pair (resource, apiKey) already existant', function() { + it('should return a 400 DUPLICATE_GROUP error', function(done) { + request(optionsCreation, function(error, response, body) { + request(optionsCreation, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('DUPLICATE_GROUP'); + done(); + }); + }); + }); + }); describe('When a creation request arrives without the fiware-service header', function() { beforeEach(function() { delete optionsCreation.headers['fiware-service']; @@ -383,11 +396,27 @@ describe('Device Group Configuration API', function() { describe('When a device group listing request arrives', function() { beforeEach(function(done) { + var optionsCreation1 = _.clone(optionsCreation), + optionsCreation2 = _.clone(optionsCreation), + optionsCreation3 = _.clone(optionsCreation); + + + optionsCreation2.json = { services: [] }; + optionsCreation3.json = { services: [] }; + + optionsCreation2.json.services[0] = _.clone(optionsCreation.json.services[0]); + optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]); + + optionsCreation2.json.services[0].apikey = 'qwertyuiop'; + optionsCreation3.json.services[0].apikey = 'lkjhgfds'; + async.series([ - async.apply(request, optionsCreation), - async.apply(request, optionsCreation), - async.apply(request, optionsCreation) - ], done); + async.apply(request, optionsCreation1), + async.apply(request, optionsCreation2), + async.apply(request, optionsCreation3) + ], function (error, results) { + done(); + }); }); it('should return a 200 OK', function(done) { From 437eaa048ce40320985d08da00534b08b5836d08 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 19:10:52 +0100 Subject: [PATCH 57/78] ADD Constrained index for group in Mongo --- lib/model/Group.js | 1 + lib/services/groupRegistryMongoDB.js | 17 ++++++++++- test/unit/mongodb-group-registry-test.js | 37 +++++++++++++++++++++--- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/lib/model/Group.js b/lib/model/Group.js index 512ffa17d..d7fcc72ba 100644 --- a/lib/model/Group.js +++ b/lib/model/Group.js @@ -41,6 +41,7 @@ var Group = new Schema({ }); function load(db) { + Group.index({ apikey: 1, resource: 1 }, { unique: true }); module.exports.model = db.model('Group', Group); module.exports.internalSchema = Group; } diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index 95f1deaae..03b03a586 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -71,7 +71,22 @@ function createGroup(group, callback) { logger.debug(context, 'Storing device group with id [%s] and type [%s]', groupObj.id, groupObj.type); - groupObj.save(saveGroupHandler(callback)); + groupObj.save(function saveHandler(error, groupDAO) { + if (error) { + if (error.code === 11000) { + logger.debug(context, 'Duplicate group entry with resource [%s] and apiKey [%s]', + group.resource, group.apikey); + + callback(new errors.DuplicateGroup(group.resource, group.apikey)); + } else { + logger.debug(context, 'Error storing device group information: %s', error); + + callback(new errors.InternalDbError(error)); + } + } else { + callback(null, groupDAO.toObject()); + } + }); } function listGroups(callback) { diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index 8191492d7..de816bd6f 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -23,6 +23,7 @@ 'use strict'; var iotAgentLib = require('../../'), + _ = require('underscore'), async = require('async'), request = require('request'), should = require('should'), @@ -196,6 +197,18 @@ describe('MongoDB Group Registry test', function() { }); }); + describe('When a new device group creation request arrives with an existant (apikey, resource) pair', function() { + it('should return a DUPLICATE_GROUP error', function(done) { + request(optionsCreation, function(error, response, body) { + request(optionsCreation, function(error, response, body) { + response.statusCode.should.equal(400); + body.name.should.equal('DUPLICATE_GROUP'); + done(); + }); + }); + }); + }); + describe('When a device group removal request arrives', function() { beforeEach(function(done) { request(optionsCreation, done); @@ -234,11 +247,27 @@ describe('MongoDB Group Registry test', function() { describe('When a device group listing request arrives', function() { beforeEach(function(done) { + var optionsCreation1 = _.clone(optionsCreation), + optionsCreation2 = _.clone(optionsCreation), + optionsCreation3 = _.clone(optionsCreation); + + + optionsCreation2.json = { services: [] }; + optionsCreation3.json = { services: [] }; + + optionsCreation2.json.services[0] = _.clone(optionsCreation.json.services[0]); + optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]); + + optionsCreation2.json.services[0].apikey = 'qwertyuiop'; + optionsCreation3.json.services[0].apikey = 'lkjhgfds'; + async.series([ - async.apply(request, optionsCreation), - async.apply(request, optionsCreation), - async.apply(request, optionsCreation) - ], done); + async.apply(request, optionsCreation1), + async.apply(request, optionsCreation2), + async.apply(request, optionsCreation3) + ], function (error, results) { + done(); + }); }); it('should return all the configured device groups from the database', function(done) { From fed70a5b0fd5fb4833538290479f28a2104346b0 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 19:12:00 +0100 Subject: [PATCH 58/78] FIX Linter errors --- lib/services/groupRegistryMemory.js | 2 +- test/unit/device-group-api-test.js | 4 +--- test/unit/mongodb-group-registry-test.js | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index b764088bf..841afe42a 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -46,7 +46,7 @@ function exists(group) { function createGroup(group, callback) { if (exists(group)) { - callback(new errors.DuplicateGroup(group.resource, group.apikey)) + callback(new errors.DuplicateGroup(group.resource, group.apikey)); } else { var storeGroup = _.clone(group); diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 273473861..ea3d1efcd 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -414,9 +414,7 @@ describe('Device Group Configuration API', function() { async.apply(request, optionsCreation1), async.apply(request, optionsCreation2), async.apply(request, optionsCreation3) - ], function (error, results) { - done(); - }); + ], done); }); it('should return a 200 OK', function(done) { diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index de816bd6f..c772504bc 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -265,9 +265,7 @@ describe('MongoDB Group Registry test', function() { async.apply(request, optionsCreation1), async.apply(request, optionsCreation2), async.apply(request, optionsCreation3) - ], function (error, results) { - done(); - }); + ], done); }); it('should return all the configured device groups from the database', function(done) { From 695d99661c769761a5dd70d82b5d2c50fe782437 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Tue, 17 Mar 2015 19:12:54 +0100 Subject: [PATCH 59/78] ADD Changes to change list --- CHANGES_NEXT_RELEASE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 88529a165..1efe6e74b 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -7,4 +7,5 @@ - FIX Lazy attributes mandatory in Device Provisioning (#51). - FIX Should allow to provision devices with unconfigured type (#50) - ADD Handler for configuration changes from the Configuration API. -- ADD Constraint to forbid multiple devices with the same ID (#48). \ No newline at end of file +- ADD Constraint to forbid multiple devices with the same ID (#48). +- ADD Constraint to ensure uniqueness of device configurations (#53). From a17298ece558599f151bc99746ae74fdd8f7d1b1 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 14:52:08 +0100 Subject: [PATCH 60/78] ADD Store the resource and apikey the device has used to access in the DB --- lib/model/Device.js | 2 ++ lib/services/deviceRegistryMongoDB.js | 2 +- test/unit/mongodb-registry-test.js | 12 ++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/model/Device.js b/lib/model/Device.js index c1d9fe0f4..f0bccc026 100644 --- a/lib/model/Device.js +++ b/lib/model/Device.js @@ -31,6 +31,8 @@ var Device = new Schema({ name: String, lazy: Array, active: Array, + apikey: String, + resource: String, staticAttributes: Array, service: String, subservice: String, diff --git a/lib/services/deviceRegistryMongoDB.js b/lib/services/deviceRegistryMongoDB.js index d76f923ea..13df9cf05 100644 --- a/lib/services/deviceRegistryMongoDB.js +++ b/lib/services/deviceRegistryMongoDB.js @@ -56,7 +56,7 @@ function saveDeviceHandler(callback) { function storeDevice(newDevice, callback) { var deviceObj = new Device.model(), attributeList = ['id', 'type', 'name', 'service', 'subservice', 'lazy', 'staticAttributes', - 'active', 'registrationId', 'internalId', 'internalAttributes']; + 'active', 'registrationId', 'internalId', 'internalAttributes', 'resource', 'apikey']; for (var i = 0; i < attributeList.length; i++) { deviceObj[attributeList[i]] = newDevice[attributeList[i]]; diff --git a/test/unit/mongodb-registry-test.js b/test/unit/mongodb-registry-test.js index 0680ae0be..4a9dcb7b7 100644 --- a/test/unit/mongodb-registry-test.js +++ b/test/unit/mongodb-registry-test.js @@ -94,11 +94,15 @@ var iotAgentLib = require('../../'), }, device1 = { id: 'light1', - type: 'Light' + type: 'Light', + resource: '/test', + apikey: '2345678ikjhgfr678i' }, device2 = { id: 'term2', - type: 'Termometer' + type: 'Termometer', + resource: '/', + apikey: 'dsf8yy789iyushu786' }, iotAgentDb; @@ -154,11 +158,15 @@ describe('MongoDB Device Registry', function() { should.exist(docs[0].staticAttributes); should.exist(docs[0].internalAttributes.customAttribute); should.exist(docs[0].active); + should.exist(docs[0].resource); + should.exist(docs[0].apikey); docs[0].active.length.should.equal(1); docs[0].staticAttributes.length.should.equal(1); docs[0].staticAttributes[0].name.should.equal('location'); docs[0].active[0].name.should.equal('pressure'); docs[0].internalAttributes.customAttribute.should.equal('customValue'); + docs[0].resource.should.equal('/test'); + docs[0].apikey.should.equal('2345678ikjhgfr678i'); done(); }); }); From bb617362fc32029c11fc3d19221e44bc92522230 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 16:09:01 +0100 Subject: [PATCH 61/78] ADD Device groups identification using the (resource, apikey) pair --- lib/fiware-iotagent-lib.js | 2 +- lib/services/groupRegistryMemory.js | 21 +++++++ lib/services/groupRegistryMongoDB.js | 50 +++++++++------ lib/services/groupService.js | 29 +-------- lib/services/ngsiService.js | 61 +++++++++++-------- test/unit/active-devices-test.js | 14 +++-- test/unit/contextBrokerSecurityAccess-test.js | 10 +-- test/unit/device-group-api-test.js | 31 +++------- 8 files changed, 115 insertions(+), 103 deletions(-) diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index 56244c9aa..1137a2426 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -70,7 +70,7 @@ function activate(newConfig, callback) { async.series([ async.apply(registry.init, newConfig), - async.apply(ngsi.init, registry, newConfig), + async.apply(ngsi.init, registry, groupRegistry, newConfig), async.apply(groupConfig.init, groupRegistry, newConfig), async.apply(security.init, newConfig), async.apply(contextServer.start, newConfig) diff --git a/lib/services/groupRegistryMemory.js b/lib/services/groupRegistryMemory.js index 841afe42a..91e1d6397 100644 --- a/lib/services/groupRegistryMemory.js +++ b/lib/services/groupRegistryMemory.js @@ -102,6 +102,26 @@ function find(service, subservice, callback) { } } +function getSingleGroup(resource, apikey, callback) { + var result; + + for (var i in registeredGroups) { + if (registeredGroups.hasOwnProperty(i) && + registeredGroups[i].resource === resource && + registeredGroups[i].apikey === apikey) { + result = registeredGroups[i]; + break; + } + } + + if (result) { + callback(null, result); + } else { + callback(new errors.DeviceGroupNotFound(resource, apikey)); + } +} + + function update(id, body, callback) { var groupToModify = registeredGroups[id]; @@ -129,6 +149,7 @@ exports.create = createGroup; exports.list = listGroups; exports.init = init; exports.find = find; +exports.get = getSingleGroup; exports.update = update; exports.remove = remove; exports.clear = clear; diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index 03b03a586..f9c2db7f5 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -121,31 +121,40 @@ function getById(id, callback) { }); } -function find(service, subservice, callback) { - var query; +function findBy(fields) { + return function() { + var query, + queryObj = {}, + i = 0, + callback; + + while(typeof arguments[i] !== 'function') { + queryObj[fields[i]] = arguments[i]; + i++; + } - logger.debug(context, 'Looking for entity with service name [%s] and subservice [%s].', service, subservice); + callback = arguments[i]; - query = Group.model.findOne({ - service: service, - subservice: subservice - }); + logger.debug(context, 'Looking for entity params %j', fields); - query.select({__v: 0}); + query = Group.model.findOne(queryObj); - query.exec(function handleGet(error, data) { - if (error) { - logger.debug(context, 'Internal MongoDB Error getting device: %s', error); + query.select({__v: 0}); - callback(new errors.InternalDbError(error)); - } else if (data) { - callback(null, data); - } else { - logger.debug(context, 'Device group for service [%s] and subservice [%s] not found.', service, subservice); + query.exec(function handleGet(error, data) { + if (error) { + logger.debug(context, 'Internal MongoDB Error getting device: %s', error); - callback(new errors.DeviceGroupNotFound(service, subservice)); - } - }); + callback(new errors.InternalDbError(error)); + } else if (data) { + callback(null, data); + } else { + logger.debug(context, 'Device group for service [%s] and subservice [%s] not found.', service, subservice); + + callback(new errors.DeviceGroupNotFound(service, subservice)); + } + }); + }; } function update(id, body, callback) { @@ -213,7 +222,8 @@ function clear(callback) { exports.create = createGroup; exports.list = listGroups; exports.init = init; -exports.find = find; +exports.find = findBy(['service', 'subservice']); +exports.get = findBy(['resource', 'apikey']); exports.update = update; exports.remove = remove; exports.clear = clear; diff --git a/lib/services/groupService.js b/lib/services/groupService.js index e0dc9b423..ad4566956 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -50,17 +50,7 @@ function createGroup(groupSet, callback) { insertedGroups.push(groupSet.services[i]); } - async.series(insertions, function(error) { - if (error) { - callback(error); - } else { - for (var j = 0; j < insertedGroups.length; j++) { - config.types[insertedGroups[j].type] = insertedGroups[j]; - } - - callback(); - } - }); + async.series(insertions, callback); } /** @@ -81,16 +71,10 @@ function remove(service, subservice, callback) { callback(null, deviceGroup.id); } - function removeFromConfig(deviceGroup, callback) { - delete config.types[deviceGroup.type]; - callback(); - } - async.waterfall([ apply(registry.find, service, subservice), extractId, - registry.remove, - removeFromConfig + registry.remove ], callback); } @@ -107,17 +91,10 @@ function update(service, subservice, body, callback) { callback(null, deviceGroup.id, body); } - function updateConfig(deviceGroup, callback) { - config.types[deviceGroup.type] = deviceGroup; - - callback(); - } - async.waterfall([ apply(registry.find, service, subservice), extractId, - registry.update, - updateConfig + registry.update ], callback); } diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 0e5c91a30..3a8f7f5ec 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -34,6 +34,7 @@ var request = require('request'), op: 'IoTAgentNGSI.NGSIService' }, registry, + groupRegistry, config; @@ -215,7 +216,7 @@ function registerDevice(deviceObj, callback) { * @param {Object} typeInformation Configuration information for the device. * @param {String} token User token to identify against the PEP Proxies (optional). */ -function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, token, callback) { +function sendUpdateValue(deviceId, attributes, typeInformation, token, callback) { var cbHost = 'http://' + config.contextBroker.host + ':' + config.contextBroker.port, options, headers = { @@ -248,7 +249,7 @@ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, toke json: { contextElements: [ { - type: deviceType, + type: typeInformation.type, isPattern: 'false', id: deviceId, attributes: attributes @@ -287,7 +288,7 @@ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, toke options.headers['fiware-servicepath'])); } else { logger.debug(context, 'Unknown error updating value'); - callback(new errors.EntityUpdateError(deviceId, deviceType)); + callback(new errors.EntityUpdateError(deviceId, typeInformation.type)); } }); } @@ -298,32 +299,40 @@ function sendUpdateValue(deviceId, deviceType, attributes, typeInformation, toke * (for preregistered devices). * * @param {String} deviceId Device ID of the device to register. - * @param {String} deviceType Type of device to register. + * @param {String} resource Resource name of the endpoint the device is calling. + * @param {String} apikey Apikey the device is using to send the values. * @param {Array} attributes Attribute array containing the values to update. * @param {Object} deviceInformation Device information object (containing security and service information). */ -function updateValue(deviceId, deviceType, attributes, deviceInformation, callback) { - var typeInformation; +function updateValue(deviceId, resource, apikey, attributes, deviceInformation, callback) { + groupRegistry.get(resource, apikey, function (error, deviceGroup) { + var typeInformation; - if (!callback) { - callback = deviceInformation; - typeInformation = config.types[deviceType]; - } else { - typeInformation = deviceInformation; - } + if (!callback) { + callback = deviceInformation; - if (config.authentication && config.authentication.enabled) { - if (typeInformation && typeInformation.trust) { - async.waterfall([ - apply(security.getToken, typeInformation.trust), - apply(sendUpdateValue, deviceId, deviceType, attributes, typeInformation) - ], callback); + if (deviceGroup) { + typeInformation = deviceGroup; + } else { + typeInformation = config.types[resource]; + } } else { - callback(new errors.SecurityInformationMissing(deviceType)); + typeInformation = deviceInformation; } - } else { - sendUpdateValue(deviceId, deviceType, attributes, typeInformation, null, callback); - } + + if (config.authentication && config.authentication.enabled) { + if (typeInformation && typeInformation.trust) { + async.waterfall([ + apply(security.getToken, typeInformation.trust), + apply(sendUpdateValue, deviceId, attributes, typeInformation) + ], callback); + } else { + callback(new errors.SecurityInformationMissing(typeInformation.type)); + } + } else { + sendUpdateValue(deviceId, attributes, typeInformation, null, callback); + } + }); } /** @@ -444,11 +453,13 @@ function getDeviceByName(deviceName, callback) { * Initializes the NGSI service. The initialization requires a configuration object and a reference to a device * registry. * - * @param {Object} newRegistry Reference to a device registry, where the devices information will be stored. - * @param {Object} newConfig Configuration object. + * @param {Object} newRegistry Reference to a device registry, where the devices information are stored. + * @param {Object} newGroupRegistry Reference to a group registry, where the groups information are stored. + * @param {Object} newConfig Configuration object. */ -function init(newRegistry, newConfig, callback) { +function init(newRegistry, newGroupRegistry, newConfig, callback) { registry = newRegistry; + groupRegistry = newGroupRegistry; config = newConfig; callback(null); diff --git a/test/unit/active-devices-test.js b/test/unit/active-devices-test.js index a6f2898e5..ead5889bb 100644 --- a/test/unit/active-devices-test.js +++ b/test/unit/active-devices-test.js @@ -39,6 +39,7 @@ var iotAgentLib = require('../../'), types: { 'Light': { commands: [], + type: 'Light', lazy: [ { name: 'temperature', @@ -53,6 +54,7 @@ var iotAgentLib = require('../../'), ] }, 'Termometer': { + type: 'Termometer', commands: [], lazy: [ { @@ -64,6 +66,7 @@ var iotAgentLib = require('../../'), ] }, 'Humidity': { + type: 'Humidity', cbHost: 'http://192.168.1.1:3024', commands: [], lazy: [], @@ -75,6 +78,7 @@ var iotAgentLib = require('../../'), ] }, 'Motion': { + type: 'Motion', commands: [], lazy: [], staticAttributes: [ @@ -137,7 +141,7 @@ describe('Active attributes test', function() { }); it('should change the value of the corresponding attribute in the context broker', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.not.exist(error); contextBrokerMock.done(); done(); @@ -161,7 +165,7 @@ describe('Active attributes test', function() { }); it('should return ENTITY_UPDATE_ERROR an error to the caller', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.exist(error); should.exist(error.name); error.name.should.equal('ENTITY_UPDATE_ERROR'); @@ -186,7 +190,7 @@ describe('Active attributes test', function() { }); it('should return BAD_REQUEST an error to the caller', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.exist(error); should.exist(error.name); error.name.should.equal('BAD_REQUEST'); @@ -211,7 +215,7 @@ describe('Active attributes test', function() { }); it('should use the Context Broker defined by the type', function(done) { - iotAgentLib.update('humSensor', 'Humidity', values, function(error) { + iotAgentLib.update('humSensor', 'Humidity', '', values, function(error) { should.not.exist(error); contextBrokerMock.done(); done(); @@ -242,7 +246,7 @@ describe('Active attributes test', function() { iotAgentLib.activate(iotAgentConfig, done); }); it('should decorate the entity with the static attributes', function(done) { - iotAgentLib.update('motion1', 'Motion', newValues, function(error) { + iotAgentLib.update('motion1', 'Motion', '', newValues, function(error) { should.not.exist(error); contextBrokerMock.done(); done(); diff --git a/test/unit/contextBrokerSecurityAccess-test.js b/test/unit/contextBrokerSecurityAccess-test.js index cbe97930b..3a75af044 100644 --- a/test/unit/contextBrokerSecurityAccess-test.js +++ b/test/unit/contextBrokerSecurityAccess-test.js @@ -49,6 +49,7 @@ var iotAgentLib = require('../../'), service: 'smartGondor', subservice: 'electricity', trust: 'BBBB987654321', + type: 'Light', commands: [], lazy: [ { @@ -65,6 +66,7 @@ var iotAgentLib = require('../../'), }, 'Termometer': { commands: [], + type: 'Termometer', lazy: [ { name: 'temp', @@ -133,14 +135,14 @@ describe('Secured access to the Context Broker', function() { }); it('should ask Keystone for a token based on the trust token', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.not.exist(error); keystoneMock.done(); done(); }); }); it('should send the generated token in the x-auth header', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.not.exist(error); contextBrokerMock.done(); done(); @@ -175,7 +177,7 @@ describe('Secured access to the Context Broker', function() { }); it('it should return a ACCESS_FORBIDDEN error to the caller', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.exist(error); error.name.should.equal('ACCESS_FORBIDDEN'); done(); @@ -207,7 +209,7 @@ describe('Secured access to the Context Broker', function() { }); it('it should return a AUTHENTICATION_ERROR error to the caller', function(done) { - iotAgentLib.update('light1', 'Light', values, function(error) { + iotAgentLib.update('light1', 'Light', '', values, function(error) { should.exist(error); error.name.should.equal('AUTHENTICATION_ERROR'); done(); diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index ea3d1efcd..229ebb741 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -185,14 +185,6 @@ describe('Device Group Configuration API', function() { }); }); }); - it('should add the device group to the statically configured ones', function(done) { - request(optionsCreation, function(error, response, body) { - /* jshint sub:true */ - - should.exist(iotAgentConfig.types['SensorMachine']); - done(); - }); - }); it('should call the configuration creation handler', function(done) { var handlerCalled = false; @@ -348,14 +340,6 @@ describe('Device Group Configuration API', function() { }); }); }); - it('should update the values in the configuration', function(done) { - request(optionsUpdate, function(error, response, body) { - /* jshint sub:true */ - - iotAgentConfig.types['SensorMachine'].cbHost.should.equal('http://anotherUnexistentHost:1026'); - done(); - }); - }); it('should call the configuration creation handler', function(done) { var handlerCalled = false; @@ -474,7 +458,9 @@ describe('Device Group Configuration API', function() { .reply(200, utils.readExampleFile('./test/unit/contextResponses/updateContext1Success.json')); - done(); + async.series([ + async.apply(request, optionsCreation) + ], done); }); afterEach(function(done) { @@ -483,11 +469,12 @@ describe('Device Group Configuration API', function() { }); it('should use the configured data', function(done) { - iotAgentLib.update('machine1', 'SensorMachine', values, function(error) { - should.not.exist(error); - contextBrokerMock.done(); - done(); - }); + iotAgentLib.update('machine1', '/deviceTest', '801230BJKL23Y9090DSFL123HJK09H324HV8732', values, + function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); }); }); }); From 748a353323617afec4717063cb43bea0997d721c Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 16:12:54 +0100 Subject: [PATCH 62/78] FIX Linter errors --- lib/errors.js | 9 ++++----- lib/services/groupRegistryMongoDB.js | 6 +++--- lib/services/ngsiService.js | 3 +-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 552f2edea..75efc52f7 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -116,13 +116,12 @@ module.exports = { this.message = 'Wrong syntax in request: ' + msg; this.code = 400; }, - DeviceGroupNotFound: function(service, subservice) { + DeviceGroupNotFound: function(fields, values) { this.name = 'DEVICE_GROUP_NOT_FOUND'; - if (subservice) { - this.message = 'Couldn\t find device group for service [' + service + - '] and subservice [' + subservice + ']'; + if (values && fields) { + this.message = 'Couldn\t find device group for fields: ' + fields + ' and values: ' + values; } else { - this.message = 'Couldn\t find device group with id [' + service + ']'; + this.message = 'Couldn\t find device group'; } this.code = 400; } diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index f9c2db7f5..3783d8da6 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -128,7 +128,7 @@ function findBy(fields) { i = 0, callback; - while(typeof arguments[i] !== 'function') { + while (typeof arguments[i] !== 'function') { queryObj[fields[i]] = arguments[i]; i++; } @@ -149,9 +149,9 @@ function findBy(fields) { } else if (data) { callback(null, data); } else { - logger.debug(context, 'Device group for service [%s] and subservice [%s] not found.', service, subservice); + logger.debug(context, 'Device group for fields [%j]not found.', fields); - callback(new errors.DeviceGroupNotFound(service, subservice)); + callback(new errors.DeviceGroupNotFound()); } }); }; diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index 3a8f7f5ec..ccf15c558 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -211,7 +211,6 @@ function registerDevice(deviceObj, callback) { * array should comply to the NGSI's attribute format. * * @param {String} deviceId Device ID of the device to register. - * @param {String} deviceType Type of device to register. * @param {Array} attributes Attribute array containing the values to update. * @param {Object} typeInformation Configuration information for the device. * @param {String} token User token to identify against the PEP Proxies (optional). @@ -305,7 +304,7 @@ function sendUpdateValue(deviceId, attributes, typeInformation, token, callback) * @param {Object} deviceInformation Device information object (containing security and service information). */ function updateValue(deviceId, resource, apikey, attributes, deviceInformation, callback) { - groupRegistry.get(resource, apikey, function (error, deviceGroup) { + groupRegistry.get(resource, apikey, function(error, deviceGroup) { var typeInformation; if (!callback) { From 4f207f841d5b7eb90fd948dc30f2adfa626d4c24 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 17:27:52 +0100 Subject: [PATCH 63/78] ADD Check the mandatory parameters --- .../deviceGroupAdministrationServer.js | 22 ++++++++++++--- lib/services/deviceProvisioningServer.js | 19 +++++++++---- lib/services/restUtils.js | 22 ++++++++------- test/unit/device-group-api-test.js | 27 +++++++++++++++++++ test/unit/mongodb-group-registry-test.js | 8 ++++++ 5 files changed, 79 insertions(+), 19 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index db3b8f37a..06c294858 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -37,6 +37,10 @@ var logger = require('fiware-node-logger'), mandatoryHeaders = [ 'fiware-service', 'fiware-servicepath' + ], + mandatoryParameters = [ + 'resource', + 'apikey' ]; @@ -180,13 +184,23 @@ function handleDeleteDeviceGroups(req, res, next) { */ function loadContextRoutes(router, name) { router.post('/iot/agents/' + name + '/services', - restUtils.checkHeaders(mandatoryHeaders), checkBody(templateGroup), handleCreateDeviceGroup); + restUtils.checkRequestAttributes('headers', mandatoryHeaders), + checkBody(templateGroup), + handleCreateDeviceGroup); + router.get('/iot/agents/' + name + '/services', - restUtils.checkHeaders(mandatoryHeaders), handleListDeviceGroups); + restUtils.checkRequestAttributes('headers', mandatoryHeaders), + handleListDeviceGroups); + router.put('/iot/agents/' + name + '/services', - restUtils.checkHeaders(mandatoryHeaders), handleModifyDeviceGroups); + restUtils.checkRequestAttributes('headers', mandatoryHeaders), + restUtils.checkRequestAttributes('query', mandatoryParameters), + handleModifyDeviceGroups); + router.delete('/iot/agents/' + name + '/services', - restUtils.checkHeaders(mandatoryHeaders), handleDeleteDeviceGroups); + restUtils.checkRequestAttributes('headers', mandatoryHeaders), + restUtils.checkRequestAttributes('query', mandatoryParameters), + handleDeleteDeviceGroups); } function setConfigurationHandler(newHandler) { diff --git a/lib/services/deviceProvisioningServer.js b/lib/services/deviceProvisioningServer.js index e99ac360b..40a75c7b8 100644 --- a/lib/services/deviceProvisioningServer.js +++ b/lib/services/deviceProvisioningServer.js @@ -199,11 +199,20 @@ function handleUpdateDevice(req, res, next) { * @param {Object} router Express request router object. */ function loadContextRoutes(router) { - router.post('/iot/devices', restUtils.checkHeaders(mandatoryHeaders), handleProvision); - router.get('/iot/devices', restUtils.checkHeaders(mandatoryHeaders), handleListDevices); - router.get('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleGetDevice); - router.put('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleUpdateDevice); - router.delete('/iot/devices/:deviceId', restUtils.checkHeaders(mandatoryHeaders), handleRemoveDevice); + router.post('/iot/devices', + restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleProvision); + + router.get('/iot/devices', + restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleListDevices); + + router.get('/iot/devices/:deviceId', + restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleGetDevice); + + router.put('/iot/devices/:deviceId', + restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleUpdateDevice); + + router.delete('/iot/devices/:deviceId', + restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleRemoveDevice); } exports.loadContextRoutes = loadContextRoutes; diff --git a/lib/services/restUtils.js b/lib/services/restUtils.js index 65aba4bb0..3f70df9ed 100644 --- a/lib/services/restUtils.js +++ b/lib/services/restUtils.js @@ -88,20 +88,22 @@ function xmlRawBody(req, res, next) { } /** - * Generates a Express middleware that checks for the pressence of all the mandatory headers, returning a BAD_REQUEST - * error if any one is not found. + * Generates a Express middleware that checks for the pressence of all the mandatory attributes of a certain kind + * (headers, query params, and so), returning a BAD_REQUEST error if any one is not found. The kind of attribute + * to check can be specified with the 'attribute' parameter. * - * @param {Array} mandatoryHeaders List of mandatory headers. - * @return {Function} next Function that check the pressence of the required headers + * @param {Array} attribute Request attribute against which the list will be matched. + * @param {Array} mandatoryAttributes List of mandatory headers. + * @return {Function} The generated middleware. */ -function checkHeaders(mandatoryHeaders) { +function checkRequestAttributes(attribute, mandatoryAttributes) { return function headerChecker(req, res, next) { - var headerKeys = _.keys(req.headers), + var headerKeys = _.keys(req[attribute]), missing = []; - for (var i = 0; i < mandatoryHeaders.length; i++) { - if (headerKeys.indexOf(mandatoryHeaders[i]) < 0) { - missing.push(mandatoryHeaders[i]); + for (var i = 0; i < mandatoryAttributes.length; i++) { + if (headerKeys.indexOf(mandatoryAttributes[i]) < 0) { + missing.push(mandatoryAttributes[i]); } } @@ -115,4 +117,4 @@ function checkHeaders(mandatoryHeaders) { exports.checkMandatoryQueryParams = checkMandatoryQueryParams; exports.xmlRawBody = xmlRawBody; -exports.checkHeaders = checkHeaders; +exports.checkRequestAttributes = checkRequestAttributes; diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 229ebb741..86c9f3a00 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -92,6 +92,10 @@ var iotAgentLib = require('../../'), headers: { 'fiware-service': 'TestService', 'fiware-servicepath': '/testingPath' + }, + qs: { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' } }, optionsUpdate = { @@ -122,6 +126,10 @@ var iotAgentLib = require('../../'), headers: { 'fiware-service': 'TestService', 'fiware-servicepath': '/testingPath' + }, + qs: { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' } }, optionsList = { @@ -378,6 +386,25 @@ describe('Device Group Configuration API', function() { }); }); + describe('When a device group update request arrives without the mandatory parameters', function() { + beforeEach(function() { + delete optionsUpdate.qs.resource; + }); + + afterEach(function() { + optionsUpdate.qs.resource = '/deviceTest'; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsUpdate, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + describe('When a device group listing request arrives', function() { beforeEach(function(done) { var optionsCreation1 = _.clone(optionsCreation), diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index c772504bc..b80b26b65 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -97,6 +97,10 @@ var iotAgentLib = require('../../'), headers: { 'fiware-service': 'TestService', 'fiware-servicepath': '/testingPath' + }, + qs: { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' } }, optionsUpdate = { @@ -127,6 +131,10 @@ var iotAgentLib = require('../../'), headers: { 'fiware-service': 'TestService', 'fiware-servicepath': '/testingPath' + }, + qs: { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' } }, optionsList = { From 8666e16829dab2fb373a783e86189bd7d7aaede0 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 19:05:13 +0100 Subject: [PATCH 64/78] ADD Look updates using the (resource, apikey) index --- lib/model/Group.js | 1 + .../deviceGroupAdministrationServer.js | 2 +- lib/services/groupRegistryMongoDB.js | 1 + lib/services/groupService.js | 10 +++--- test/unit/device-group-api-test.js | 36 ++++++++++++++++--- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/lib/model/Group.js b/lib/model/Group.js index d7fcc72ba..eb2c855d0 100644 --- a/lib/model/Group.js +++ b/lib/model/Group.js @@ -28,6 +28,7 @@ var mongoose = require('mongoose'), var Group = new Schema({ id: String, url: String, + resource: String, apikey: String, type: String, service: String, diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 06c294858..0406f3f49 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -146,7 +146,7 @@ function handleListDeviceGroups(req, res, next) { */ function handleModifyDeviceGroups(req, res, next) { async.series([ - apply(groupService.update, req.headers['fiware-service'], req.headers['fiware-servicepath'], req.body), + apply(groupService.update, req.query['resource'], req.query['apikey'], req.body), apply(applyConfigurationHandler, req.body) ], function(error) { if (error) { diff --git a/lib/services/groupRegistryMongoDB.js b/lib/services/groupRegistryMongoDB.js index 3783d8da6..eff3dc82f 100644 --- a/lib/services/groupRegistryMongoDB.js +++ b/lib/services/groupRegistryMongoDB.js @@ -53,6 +53,7 @@ function createGroup(group, callback) { attributeList = [ 'id', 'url', + 'resource', 'apikey', 'type', 'service', diff --git a/lib/services/groupService.js b/lib/services/groupService.js index ad4566956..cc907eea8 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -79,20 +79,20 @@ function remove(service, subservice, callback) { } /** - * Update the device group defined by a service and subservice with the values in the new body. The new body does not + * 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 Group resource name. + * @param {String} apikey Group apikey name. * @param {Object} body New body containing the attributes to change. */ -function update(service, subservice, body, callback) { +function update(resource, apikey, body, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id, body); } async.waterfall([ - apply(registry.find, service, subservice), + apply(registry.get, resource, apikey), extractId, registry.update ], callback); diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 86c9f3a00..ae0a1a2dc 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -328,7 +328,25 @@ describe('Device Group Configuration API', function() { describe('When a device group update request arrives', function() { beforeEach(function(done) { - request(optionsCreation, done); + var optionsCreation1 = _.clone(optionsCreation), + optionsCreation2 = _.clone(optionsCreation), + optionsCreation3 = _.clone(optionsCreation); + + + optionsCreation1.json = { services: [] }; + optionsCreation3.json = { services: [] }; + + optionsCreation1.json.services[0] = _.clone(optionsCreation.json.services[0]); + optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]); + + optionsCreation1.json.services[0].apikey = 'qwertyuiop'; + optionsCreation3.json.services[0].apikey = 'lkjhgfds'; + + async.series([ + async.apply(request, optionsCreation1), + async.apply(request, optionsCreation2), + async.apply(request, optionsCreation3) + ], done); }); it('should return a 200 OK', function(done) { @@ -339,11 +357,21 @@ describe('Device Group Configuration API', function() { }); }); - it('should update the values in the database', function(done) { + it('should update the appropriate values in the database', function(done) { request(optionsUpdate, function(error, response, body) { request(optionsList, function(error, response, body) { - body.count.should.equal(1); - body.services[0].cbHost.should.equal('http://anotherUnexistentHost:1026'); + var found = false; + body.count.should.equal(3); + + for (var i = 0; i < body.services.length; i++) { + if (body.services[i].apikey === '801230BJKL23Y9090DSFL123HJK09H324HV8732' && + body.services[i].resource === '/deviceTest') { + body.services[i].cbHost.should.equal('http://anotherUnexistentHost:1026'); + found = true; + } + } + + found.should.equal(true); done(); }); }); From 03bdbde3c0de74fb842c8189d648f5be0e108506 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 18 Mar 2015 19:06:01 +0100 Subject: [PATCH 65/78] FIX Linter errors --- lib/services/deviceGroupAdministrationServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 0406f3f49..57d7812d3 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -146,7 +146,7 @@ function handleListDeviceGroups(req, res, next) { */ function handleModifyDeviceGroups(req, res, next) { async.series([ - apply(groupService.update, req.query['resource'], req.query['apikey'], req.body), + apply(groupService.update, req.query.resource, req.query.apikey, req.body), apply(applyConfigurationHandler, req.body) ], function(error) { if (error) { From f8bb50252d733ed68e279e18c33265e5691105bf Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 15:57:51 +0100 Subject: [PATCH 66/78] ADD APIKey and resource based group removal --- .../deviceGroupAdministrationServer.js | 4 +- lib/services/groupService.js | 8 +-- test/unit/device-group-api-test.js | 59 +++++++++++++++++++ test/unit/mongodb-group-registry-test.js | 24 ++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 57d7812d3..3cbf9a174 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -155,8 +155,6 @@ function handleModifyDeviceGroups(req, res, next) { res.status(200).send({}); } }); - - } /** @@ -168,7 +166,7 @@ function handleModifyDeviceGroups(req, res, next) { */ function handleDeleteDeviceGroups(req, res, next) { - groupService.remove(req.headers['fiware-service'], req.headers['fiware-servicepath'], function(error) { + groupService.remove(req.query.resource, req.query.apikey, function(error) { if (error) { next(error); } else { diff --git a/lib/services/groupService.js b/lib/services/groupService.js index cc907eea8..5abde4d70 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -63,16 +63,16 @@ function listGroups(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 Group service name. + * @param {String} apikey Group subservice name. */ -function remove(service, subservice, callback) { +function remove(resource, apikey, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id); } async.waterfall([ - apply(registry.find, service, subservice), + apply(registry.get, resource, apikey), extractId, registry.remove ], callback); diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index ae0a1a2dc..42b3d189d 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -307,6 +307,43 @@ describe('Device Group Configuration API', function() { }); }); + describe('When a device group removal arrives to a DB with three groups', function() { + beforeEach(function(done) { + var optionsCreation1 = _.clone(optionsCreation), + optionsCreation2 = _.clone(optionsCreation), + optionsCreation3 = _.clone(optionsCreation); + + optionsCreation1.json = { services: [] }; + optionsCreation3.json = { services: [] }; + + optionsCreation1.json.services[0] = _.clone(optionsCreation.json.services[0]); + optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]); + + optionsCreation1.json.services[0].apikey = 'qwertyuiop'; + optionsCreation3.json.services[0].apikey = 'lkjhgfds'; + + async.series([ + async.apply(request, optionsCreation1), + async.apply(request, optionsCreation2), + async.apply(request, optionsCreation3) + ], done); + }); + + it('should remove just the selected group', function(done) { + request(optionsDelete, function(error, response, body) { + request(optionsList, function(error, response, body) { + body.count.should.equal(2); + + for (var i = 0; i < body.services.length; i++) { + body.services[i].apikey.should.not.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732'); + } + + done(); + }); + }); + }); + }); + describe('When a device group removal request arrives without the mandatory headers', function() { beforeEach(function() { delete optionsDelete.headers['fiware-servicepath']; @@ -326,6 +363,28 @@ describe('Device Group Configuration API', function() { }); }); + describe('When a device group removal request arrives without the mandatory parameters', function() { + beforeEach(function() { + delete optionsDelete.qs; + }); + + afterEach(function() { + optionsDelete.qs = { + resource: '/deviceTest', + apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' + }; + }); + + it('should fail with a 400 MISSING_HEADERS Error', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(400); + body.name.should.equal('MISSING_HEADERS'); + done(); + }); + }); + }); + describe('When a device group update request arrives', function() { beforeEach(function(done) { var optionsCreation1 = _.clone(optionsCreation), diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index b80b26b65..248aae638 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -24,6 +24,7 @@ var iotAgentLib = require('../../'), _ = require('underscore'), + utils = require('../tools/utils'), async = require('async'), request = require('request'), should = require('should'), @@ -253,6 +254,29 @@ describe('MongoDB Group Registry test', function() { }); }); + describe('When a multiple device group creation arrives', function() { + var optionsMultipleCreation = _.clone(optionsCreation), + optionsMultipleUpdate = _.clone(optionsUpdate); + + beforeEach(function(done) { + optionsMultipleCreation.json = utils.readExampleFile( + './test/unit/groupProvisioningRequests/multipleGroupsCreation.json'); + + done(); + }); + + it('should create the values in the database', function(done) { + request(optionsMultipleCreation, function(error, response, body) { + iotAgentDb.collection('groups').find({}).toArray(function(err, docs) { + should.not.exist(err); + should.exist(docs); + docs.length.should.equal(2); + done(); + }); + }); + }); + }); + describe('When a device group listing request arrives', function() { beforeEach(function(done) { var optionsCreation1 = _.clone(optionsCreation), From 15deb343ddc3d82212d77a5356d78f3442d25abe Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 16:07:41 +0100 Subject: [PATCH 67/78] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09610c30d..4f2972612 100644 --- a/README.md +++ b/README.md @@ -505,6 +505,8 @@ Both approaches are better described in the sections bellow. ### Configuration API The following sections show the available operations for the Configuration API. Every operation in the API require the `fiware-service` and `fiware-servicepath` to be defined; the operations are performed in the scope of those headers. For the list case, the special wildcard servicepath can be specified, '/*'. In this case, the operation applies to all the subservices of the service given by the `fiware-service` header. +For every Device Group, the pair (resource, apikey) *must* be unique (as it is used to identify which group to assign to which device). Those operations of the API targeting specific resources will need the use of the `resource` and `apikey` parameters to select the apropriate instance. + #### Device Group Model Device groups contain the following attributes: * **service**: service of the devices of this type. @@ -574,7 +576,7 @@ Returns: * 500 SERVER ERROR if there was any error not contemplated above. #### PUT /iot/agents/:agentName/services -Modifies the information for a device group configuration, identified by its service and subservice. Takes a device group body as the payload. The body does not have to be complete: for incomplete bodies, just the existing attributes will be updated +Modifies the information for a device group configuration, identified by the `resource` and `apikey` query parameters. Takes a device group body as the payload. The body does not have to be complete: for incomplete bodies, just the existing attributes will be updated E.g.: ``` @@ -590,7 +592,7 @@ Returns: * 500 SERVER ERROR if there was any error not contemplated above. #### DELETE /iot/agents/:agentName/services -Removes a device group configuration from the DB, specified by the service and subservice headers. +Removes a device group configuration from the DB, specified by the `resource` and `apikey` query parameters. Returns: * 200 OK if successful. From c8d6ed75fb1e418bdac3c477aef182f14229953b Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 16:17:56 +0100 Subject: [PATCH 68/78] FIX Linter errors --- test/unit/device-group-api-test.js | 2 +- test/unit/mongodb-group-registry-test.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 42b3d189d..3cbc757bc 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -369,7 +369,7 @@ describe('Device Group Configuration API', function() { }); afterEach(function() { - optionsDelete.qs = { + optionsDelete.qs = { resource: '/deviceTest', apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732' }; diff --git a/test/unit/mongodb-group-registry-test.js b/test/unit/mongodb-group-registry-test.js index 248aae638..80f345c0c 100644 --- a/test/unit/mongodb-group-registry-test.js +++ b/test/unit/mongodb-group-registry-test.js @@ -255,8 +255,7 @@ describe('MongoDB Group Registry test', function() { }); describe('When a multiple device group creation arrives', function() { - var optionsMultipleCreation = _.clone(optionsCreation), - optionsMultipleUpdate = _.clone(optionsUpdate); + var optionsMultipleCreation = _.clone(optionsCreation); beforeEach(function(done) { optionsMultipleCreation.json = utils.readExampleFile( From b7ce6ffdeae56230c16b1969e3c76f4e782bcc94 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 16:39:50 +0100 Subject: [PATCH 69/78] ADD Protect remove operations with the service and subservice information --- lib/errors.js | 5 ++ .../deviceGroupAdministrationServer.js | 17 ++++--- lib/services/groupService.js | 22 +++++++-- test/unit/device-group-api-test.js | 47 +++++++++++++++++++ .../multipleGroupsCreation.json | 44 +++++++++++++++++ .../multipleGroupsUpdate.json | 16 +++++++ 6 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 test/unit/groupProvisioningRequests/multipleGroupsCreation.json create mode 100644 test/unit/groupProvisioningRequests/multipleGroupsUpdate.json diff --git a/lib/errors.js b/lib/errors.js index 75efc52f7..b93e74330 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -111,6 +111,11 @@ module.exports = { this.message = 'Some headers were missing from the request: ' + msg; this.code = 400; }, + MismatchedService: function(service, subservice) { + this.name = 'MISMATCHED_SERVICE'; + this.message = 'The declared service didn\'t match the stored one in the entity'; + this.code = 403; + }, WrongSyntax: function(msg) { this.name = 'WRONG_SYNTAX'; this.message = 'Wrong syntax in request: ' + msg; diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 3cbf9a174..054ced080 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -166,13 +166,16 @@ function handleModifyDeviceGroups(req, res, next) { */ function handleDeleteDeviceGroups(req, res, next) { - groupService.remove(req.query.resource, req.query.apikey, function(error) { - if (error) { - next(error); - } else { - res.status(200).send({}); - } - }); + groupService.remove( + req.headers['fiware-service'], req.headers['fiware-servicepath'], + req.query.resource, req.query.apikey, + function(error) { + if (error) { + next(error); + } else { + res.status(200).send({}); + } + }); } /** diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 5abde4d70..30a9e0da6 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -26,6 +26,7 @@ var async = require('async'), apply = async.apply, logger = require('fiware-node-logger'), + errors = require('../errors'), context = { op: 'IoTAgentNGSI.DeviceGroupService' }, @@ -63,16 +64,27 @@ function listGroups(callback) { /** * Remove the device group defined by the given service and subservice names from the group registry. * - * @param {String} resource Group service name. - * @param {String} apikey Group subservice name. + * @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(resource, apikey, callback) { +function remove(service, subservice, resource, apikey, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id); } + function checkServiceIdentity(deviceGroup, callback) { + if (deviceGroup.service === service && deviceGroup.subservice === subservice) { + callback(null, deviceGroup); + } else { + callback(new errors.MismatchedService(service, subservice)); + } + } + async.waterfall([ apply(registry.get, resource, apikey), + checkServiceIdentity, extractId, registry.remove ], callback); @@ -82,8 +94,8 @@ function remove(resource, apikey, 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} resource Group resource name. - * @param {String} apikey Group apikey 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(resource, apikey, body, callback) { diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index 3cbc757bc..f098e75a9 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -307,6 +307,53 @@ describe('Device Group Configuration API', function() { }); }); + describe('When a device group removal arrives declaring a different service', function() { + var optionsDeleteDifferentService = _.clone(optionsDelete); + + beforeEach(function(done) { + optionsDeleteDifferentService.headers['fiware-service'] = 'unexistentService'; + request(optionsCreation, done); + }); + + afterEach(function(done) { + optionsDeleteDifferentService.headers['fiware-service'] = 'TestService'; + done(); + }); + + it('should return a 403 MISMATCHED_SERVICE error', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(403); + body.name.should.equal('MISMATCHED_SERVICE'); + done(); + }); + }); + }); + + describe('When a device group removal arrives declaring a different subservice', function() { + var optionsDeleteDifferentService = _.clone(optionsDelete); + + beforeEach(function(done) { + optionsDeleteDifferentService.headers['fiware-servicepath'] = '/unexistentSubservice'; + request(optionsCreation, done); + }); + + afterEach(function(done) { + optionsDeleteDifferentService.headers['fiware-servicepath'] = '/testingPath'; + done(); + }); + + it('should return a 403 MISMATCHED_SERVICE error', function(done) { + request(optionsDelete, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(403); + body.name.should.equal('MISMATCHED_SERVICE'); + done(); + }); + }); + }); + + describe('When a device group removal arrives to a DB with three groups', function() { beforeEach(function(done) { var optionsCreation1 = _.clone(optionsCreation), diff --git a/test/unit/groupProvisioningRequests/multipleGroupsCreation.json b/test/unit/groupProvisioningRequests/multipleGroupsCreation.json new file mode 100644 index 000000000..649f3fb7e --- /dev/null +++ b/test/unit/groupProvisioningRequests/multipleGroupsCreation.json @@ -0,0 +1,44 @@ +{ + "services": [ + { + "resource": "/deviceTest", + "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732", + "type": "Light", + "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS", + "cbHost": "http://unexistentHost:1026", + "commands": [ + { + "name": "wheel1", + "type": "Wheel" + } + ], + "lazy": [ + { + "name": "luminescence", + "type": "Lumens" + } + ], + "active": [ + { + "name": "status", + "type": "Boolean" + } + ] + }, + { + "resource": "/deviceTest", + "apikey": "23HJK3Y9090DSFL173209HV8801232", + "type": "Termometer", + "trust": "BL9803H3QRW8342HBAMS8A8", + "cbHost": "http://unexistentHost:1026", + "commands": [ + { + "name": "temperature", + "type": "degrees" + } + ], + "lazy": [], + "active": [] + } + ] +} \ No newline at end of file diff --git a/test/unit/groupProvisioningRequests/multipleGroupsUpdate.json b/test/unit/groupProvisioningRequests/multipleGroupsUpdate.json new file mode 100644 index 000000000..a75957ce9 --- /dev/null +++ b/test/unit/groupProvisioningRequests/multipleGroupsUpdate.json @@ -0,0 +1,16 @@ +{ + "services": [ + { + "resource": "/deviceTest", + "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732", + "type": "Light", + "trust": "TheNewTrust" + }, + { + "resource": "/deviceTest", + "apikey": "23HJK3Y9090DSFL173209HV8801232", + "type": "Termometer", + "trust": "TheNewTrust" + } + ] +} \ No newline at end of file From b8f9da4be722d0f6e23b325d63fcbc2134b2db78 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 17:01:07 +0100 Subject: [PATCH 70/78] ADD Protect the modify group operation with the service information --- .../deviceGroupAdministrationServer.js | 3 +- lib/services/groupService.js | 23 +++++----- test/unit/device-group-api-test.js | 45 +++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/lib/services/deviceGroupAdministrationServer.js b/lib/services/deviceGroupAdministrationServer.js index 054ced080..b35469917 100644 --- a/lib/services/deviceGroupAdministrationServer.js +++ b/lib/services/deviceGroupAdministrationServer.js @@ -146,7 +146,8 @@ function handleListDeviceGroups(req, res, next) { */ function handleModifyDeviceGroups(req, res, next) { async.series([ - apply(groupService.update, req.query.resource, req.query.apikey, req.body), + apply(groupService.update, req.headers['fiware-service'], req.headers['fiware-servicepath'], + req.query.resource, req.query.apikey, req.body), apply(applyConfigurationHandler, req.body) ], function(error) { if (error) { diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 30a9e0da6..6b3b07712 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -61,6 +61,14 @@ function listGroups(callback) { registry.list(callback); } +function checkServiceIdentity(service, subservice, deviceGroup, callback) { + if (deviceGroup.service === service && deviceGroup.subservice === subservice) { + callback(null, deviceGroup); + } else { + callback(new errors.MismatchedService(service, subservice)); + } +} + /** * Remove the device group defined by the given service and subservice names from the group registry. * @@ -74,17 +82,9 @@ function remove(service, subservice, resource, apikey, callback) { callback(null, deviceGroup.id); } - function checkServiceIdentity(deviceGroup, callback) { - if (deviceGroup.service === service && deviceGroup.subservice === subservice) { - callback(null, deviceGroup); - } else { - callback(new errors.MismatchedService(service, subservice)); - } - } - async.waterfall([ apply(registry.get, resource, apikey), - checkServiceIdentity, + apply(checkServiceIdentity, service, subservice), extractId, registry.remove ], callback); @@ -94,17 +94,20 @@ function remove(service, subservice, resource, apikey, 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(resource, apikey, body, callback) { +function update(service, subservice, resource, apikey, body, callback) { function extractId(deviceGroup, callback) { callback(null, deviceGroup.id, body); } async.waterfall([ apply(registry.get, resource, apikey), + apply(checkServiceIdentity, service, subservice), extractId, registry.update ], callback); diff --git a/test/unit/device-group-api-test.js b/test/unit/device-group-api-test.js index f098e75a9..0e9f3b1cf 100644 --- a/test/unit/device-group-api-test.js +++ b/test/unit/device-group-api-test.js @@ -501,6 +501,51 @@ describe('Device Group Configuration API', function() { }); }); + + + + describe('When a device group update request arrives declaring a different service', function() { + beforeEach(function(done) { + optionsUpdate.headers['fiware-service'] = 'UnexistentService'; + request(optionsCreation, done); + }); + + afterEach(function() { + optionsUpdate.headers['fiware-service'] = 'TestService'; + }); + + + it('should return a 200 OK', function(done) { + request(optionsUpdate, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(403); + body.name.should.equal('MISMATCHED_SERVICE'); + done(); + }); + }); + }); + + describe('When a device group update request arrives declaring a different subservice', function() { + beforeEach(function(done) { + optionsUpdate.headers['fiware-servicepath'] = '/UnexistentServicepath'; + request(optionsCreation, done); + }); + + afterEach(function() { + optionsUpdate.headers['fiware-servicepath'] = '/testingPath'; + }); + + + it('should return a 200 OK', function(done) { + request(optionsUpdate, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(403); + body.name.should.equal('MISMATCHED_SERVICE'); + done(); + }); + }); + }); + describe('When a device group update request arrives without the mandatory headers', function() { beforeEach(function() { delete optionsUpdate.headers['fiware-servicepath']; From 06ea6cc6cb30a823ad10b6b4e9d6f2e6df02ff1f Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 18:13:19 +0100 Subject: [PATCH 71/78] ADD Guard against lack of type in type definitions --- lib/errors.js | 1 + lib/services/ngsiService.js | 5 +++++ test/unit/active-devices-test.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/lib/errors.js b/lib/errors.js index b93e74330..e57ee3295 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -61,6 +61,7 @@ module.exports = { TypeNotFound: function(id, type) { this.name = 'TYPE_NOT_FOUND'; this.message = 'Type : ' + type + ' not found for device with id: ' + id; + this.code = 500; }, MissingAttributes: function(msg) { this.name = 'MISSING_ATTRIBUTES'; diff --git a/lib/services/ngsiService.js b/lib/services/ngsiService.js index ccf15c558..1b73ac992 100644 --- a/lib/services/ngsiService.js +++ b/lib/services/ngsiService.js @@ -242,6 +242,11 @@ function sendUpdateValue(deviceId, attributes, typeInformation, token, callback) } } + if (!typeInformation || !typeInformation.type) { + callback(new errors.TypeNotFound(null, deviceId)); + return; + } + options = { url: cbHost + '/v1/updateContext', method: 'POST', diff --git a/test/unit/active-devices-test.js b/test/unit/active-devices-test.js index ead5889bb..c1f8df6ef 100644 --- a/test/unit/active-devices-test.js +++ b/test/unit/active-devices-test.js @@ -53,6 +53,21 @@ var iotAgentLib = require('../../'), } ] }, + 'BrokenLight': { + commands: [], + lazy: [ + { + name: 'temperature', + type: 'centigrades' + } + ], + active: [ + { + name: 'pressure', + type: 'Hgmm' + } + ] + }, 'Termometer': { type: 'Termometer', commands: [], @@ -149,6 +164,23 @@ describe('Active attributes test', function() { }); }); + describe('When the IoT Agent receives information from a device whose type doesn\'t have a type name', function() { + beforeEach(function(done) { + nock.cleanAll(); + + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should fail with a 500 TYPE_NOT_FOUND error', function(done) { + iotAgentLib.update('light1', 'BrokenLight', '', values, function(error) { + should.exist(error); + error.code.should.equal(500); + error.name.should.equal('TYPE_NOT_FOUND'); + done(); + }); + }); + }); + describe('When the Context Broker returns an HTTP error code updating an entity', function() { beforeEach(function(done) { nock.cleanAll(); From 8803834e9fbd495dc31586e43e812fa3594fefbf Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Mon, 23 Mar 2015 18:53:15 +0100 Subject: [PATCH 72/78] ADD Get group operation --- lib/fiware-iotagent-lib.js | 1 + lib/services/groupService.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index 1137a2426..1761ea6a3 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -93,6 +93,7 @@ exports.update = ngsi.update; exports.listDevices = ngsi.listDevices; exports.getDevice = ngsi.getDevice; exports.getDeviceByName = ngsi.getDeviceByName; +exports.getConfiguration = groupConfig.get; exports.setDataUpdateHandler = contextServer.setUpdateHandler; exports.setDataQueryHandler = contextServer.setQueryHandler; exports.setConfigurationHandler = contextServer.setConfigurationHandler; diff --git a/lib/services/groupService.js b/lib/services/groupService.js index 6b3b07712..086ee71d2 100644 --- a/lib/services/groupService.js +++ b/lib/services/groupService.js @@ -123,6 +123,16 @@ function find(service, subservice, callback) { registry.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) { + registry.get(resource, apikey, callback); +} + /** * Initializes the device Group service. The initialization requires a configuration object and a reference to a device * registry. @@ -141,5 +151,6 @@ exports.init = init; exports.create = createGroup; exports.list = listGroups; exports.find = find; +exports.get = getGroup; exports.update = update; exports.remove = remove; From 675fc9f9434d298a6be14782016bcab107a76951 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 25 Mar 2015 10:24:32 +0100 Subject: [PATCH 73/78] ADD Refactor the Query handlers in Context Server --- lib/services/contextServer.js | 86 +++++++++++++++++----------------- test/unit/lazy-devices-test.js | 78 ++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index a8b003957..4a38e6415 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -210,63 +210,61 @@ function handleUpdate(req, res, next) { * each one of them a call to the user query handler function is made. */ function handleQuery(req, res, next) { - function addStaticAttributes(id, type, contextElement, callback) { - ngsi.getDeviceByName(id, function handleFindDevice(error, device) { - if (error) { - callback(error); - } else { - if (device.staticAttributes) { - contextElement.attributes = contextElement.attributes.concat(device.staticAttributes); - } + function addStaticAttributes(device, contextElement, callback) { + if (device.staticAttributes) { + contextElement.attributes = contextElement.attributes.concat(device.staticAttributes); + } - callback(null, contextElement); - } - }); + callback(null, contextElement); } - if (queryHandler) { - var queryRequests = []; - - logger.debug(context, 'Handling query from [%s]', req.get('host')); - - for (var i = 0; i < req.body.entities.length; i++) { + function createQueryRequests(attributes, contextEntity, callback) { + ngsi.getDeviceByName(contextEntity.id, function handleFindDevice(error, device) { var executeQueryHandler = apply( - queryHandler, - req.body.entities[i].id, - req.body.entities[i].type, + queryHandler, + contextEntity.id, + contextEntity.type, req.body.attributes ), executeAddStaticAttributes = apply( addStaticAttributes, - req.body.entities[i].id, - req.body.entities[i].type + device ); - queryRequests.push( - async.apply(async.waterfall, [ - executeQueryHandler, - executeAddStaticAttributes - ]) - ); - } + callback(error, apply(async.waterfall, [ + executeQueryHandler, + executeAddStaticAttributes + ])); + }); + } - async.series(queryRequests, function(error, result) { - if (error) { - logger.debug(context, 'There was an error handling the query: %s.', error); - next(error); - } else { - logger.debug(context, 'Query from [%s] handled successfully.', req.get('host')); + function handleQueryContextRequests(error, result) { + if (error) { + logger.debug(context, 'There was an error handling the query: %s.', error); + next(error); + } else { + logger.debug(context, 'Query from [%s] handled successfully.', req.get('host')); - if (req.is('application/xml')) { - res.set('Content-Type', 'application/xml'); - res - .status(200) - .send(generateXmlResponse(queryTemplates)(createQueryResponse(req, res, result))); - } else { - res.status(200).json(createQueryResponse(req, res, result)); - } + if (req.is('application/xml')) { + res.set('Content-Type', 'application/xml'); + res.status(200) + .send(generateXmlResponse(queryTemplates)(createQueryResponse(req, res, result))); + } else { + res.status(200).json(createQueryResponse(req, res, result)); } - }); + } + } + + if (queryHandler) { + var queryRequests = []; + + logger.debug(context, 'Handling query from [%s]', req.get('host')); + + async.waterfall([ + apply(async.map, req.body.entities, apply(createQueryRequests, req.body.attributes)), + async.series + ], handleQueryContextRequests) + } else { var errorNotFound = new Error({ message: 'Query handler not found' diff --git a/test/unit/lazy-devices-test.js b/test/unit/lazy-devices-test.js index 793d2ea8c..59edbdfc6 100644 --- a/test/unit/lazy-devices-test.js +++ b/test/unit/lazy-devices-test.js @@ -29,6 +29,7 @@ var iotAgentLib = require('../../'), should = require('should'), logger = require('fiware-node-logger'), nock = require('nock'), + mongoUtils = require('./mongoDBUtils'), request = require('request'), contextBrokerMock, iotAgentConfig = { @@ -105,7 +106,11 @@ describe('IoT Agent Lazy Devices', function() { }); afterEach(function(done) { - iotAgentLib.deactivate(done); + iotAgentLib.clearAll(function () { + iotAgentLib.deactivate(function() { + mongoUtils.cleanDbs(done); + }); + }); }); describe('When the IoT Agent receives an update on the device data in JSON format', function() { @@ -244,7 +249,10 @@ describe('IoT Agent Lazy Devices', function() { .reply(200, utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); - iotAgentLib.activate(iotAgentConfig, done); + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); }); it('should call the device handler with the received data', function(done) { @@ -352,7 +360,10 @@ describe('IoT Agent Lazy Devices', function() { .reply(200, utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); - iotAgentLib.activate(iotAgentConfig, done); + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); }); it('should return the information querying the underlying devices', function(done) { @@ -374,6 +385,67 @@ describe('IoT Agent Lazy Devices', function() { }); }); + describe.skip('When a query arrives to the IoT Agent without any attributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', + method: 'POST', + json: { + entities: [ + { + type: 'Light', + isPattern: 'false', + id: 'light1' + } + ] + } + }, + sensorData = [ + { + id: 'light1', + isPattern: false, + type: 'Light', + attributes: [ + { + name: 'dimming', + type: 'Percentage', + value: 19 + } + ] + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); + + iotAgentLib.activate(iotAgentConfig, done); + }); + + it('should return the information of all the attributes', function(done) { + var expectedResponse = utils + .readExampleFile('./test/unit/contextProviderResponses/queryInformationResponse.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, attributes, callback) { + attributes.length.should.equal(1); + attributes[0].should.equal('dimming'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); + describe('When a context query arrives to the IoT Agent for a type with static attributes', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', From 1ae64b38251757cf11449790fc58583696a48093 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 25 Mar 2015 10:26:11 +0100 Subject: [PATCH 74/78] FIX Linter errors --- lib/services/contextServer.js | 4 +--- test/unit/lazy-devices-test.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index 4a38e6415..c0210586e 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -256,14 +256,12 @@ function handleQuery(req, res, next) { } if (queryHandler) { - var queryRequests = []; - logger.debug(context, 'Handling query from [%s]', req.get('host')); async.waterfall([ apply(async.map, req.body.entities, apply(createQueryRequests, req.body.attributes)), async.series - ], handleQueryContextRequests) + ], handleQueryContextRequests); } else { var errorNotFound = new Error({ diff --git a/test/unit/lazy-devices-test.js b/test/unit/lazy-devices-test.js index 59edbdfc6..a81d25d47 100644 --- a/test/unit/lazy-devices-test.js +++ b/test/unit/lazy-devices-test.js @@ -106,7 +106,7 @@ describe('IoT Agent Lazy Devices', function() { }); afterEach(function(done) { - iotAgentLib.clearAll(function () { + iotAgentLib.clearAll(function() { iotAgentLib.deactivate(function() { mongoUtils.cleanDbs(done); }); From 61423f09128be42b9674da31ebe64c7d7a846120 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 25 Mar 2015 10:42:12 +0100 Subject: [PATCH 75/78] ADD Global queries for Context provider queries --- lib/services/contextServer.js | 25 ++++++++++++++++++++++--- test/unit/lazy-devices-test.js | 16 ++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index c0210586e..2e952a243 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -210,6 +210,10 @@ function handleUpdate(req, res, next) { * each one of them a call to the user query handler function is made. */ function handleQuery(req, res, next) { + function getName(element) { + return element.name; + } + function addStaticAttributes(device, contextElement, callback) { if (device.staticAttributes) { contextElement.attributes = contextElement.attributes.concat(device.staticAttributes); @@ -218,13 +222,27 @@ function handleQuery(req, res, next) { callback(null, contextElement); } + function completeAttributes(attributes, device, callback) { + if (attributes) { + callback(null, attributes); + } else if (device.lazy) { + callback(null, device.lazy.map(getName)); + } else { + callback(null, null); + } + } + function createQueryRequests(attributes, contextEntity, callback) { ngsi.getDeviceByName(contextEntity.id, function handleFindDevice(error, device) { - var executeQueryHandler = apply( + var executeCompleteAttributes = apply( + completeAttributes, + attributes, + device + ), + executeQueryHandler = apply( queryHandler, contextEntity.id, - contextEntity.type, - req.body.attributes + contextEntity.type ), executeAddStaticAttributes = apply( addStaticAttributes, @@ -232,6 +250,7 @@ function handleQuery(req, res, next) { ); callback(error, apply(async.waterfall, [ + executeCompleteAttributes, executeQueryHandler, executeAddStaticAttributes ])); diff --git a/test/unit/lazy-devices-test.js b/test/unit/lazy-devices-test.js index a81d25d47..0045d0d65 100644 --- a/test/unit/lazy-devices-test.js +++ b/test/unit/lazy-devices-test.js @@ -385,7 +385,7 @@ describe('IoT Agent Lazy Devices', function() { }); }); - describe.skip('When a query arrives to the IoT Agent without any attributes', function() { + describe('When a query arrives to the IoT Agent without any attributes', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', method: 'POST', @@ -406,8 +406,8 @@ describe('IoT Agent Lazy Devices', function() { type: 'Light', attributes: [ { - name: 'dimming', - type: 'Percentage', + name: 'temperature', + type: 'centigrades', value: 19 } ] @@ -425,16 +425,20 @@ describe('IoT Agent Lazy Devices', function() { .reply(200, utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); - iotAgentLib.activate(iotAgentConfig, done); + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); }); it('should return the information of all the attributes', function(done) { var expectedResponse = utils - .readExampleFile('./test/unit/contextProviderResponses/queryInformationResponse.json'); + .readExampleFile('./test/unit/contextProviderResponses/queryInformationResponseEmptyAttributes.json'); iotAgentLib.setDataQueryHandler(function(id, type, attributes, callback) { + should.exist(attributes); attributes.length.should.equal(1); - attributes[0].should.equal('dimming'); + attributes[0].should.equal('temperature'); callback(null, sensorData[0]); }); From a818d0610b2908d26663e0484fa8a6295cf22a18 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 25 Mar 2015 12:22:00 +0100 Subject: [PATCH 76/78] ADD New log entries for attribute selection in queries --- lib/services/contextServer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index 2e952a243..58ef83125 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -224,10 +224,13 @@ function handleQuery(req, res, next) { function completeAttributes(attributes, device, callback) { if (attributes) { + logger.debug(context, 'Handling received set of attributes: %j', attributes); callback(null, attributes); } else if (device.lazy) { + logger.debug(context, 'Handling stored set of attributes: %j', attributes); callback(null, device.lazy.map(getName)); } else { + logger.debug(context, 'Couldn\'t find any attributes. Handling with null reference'); callback(null, null); } } From 8f4b5037d2d86687f9905b0c0c04bd734596afff Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Wed, 25 Mar 2015 12:54:08 +0100 Subject: [PATCH 77/78] FIX Empty attributes array treated as unexistent --- lib/services/contextServer.js | 2 +- test/unit/lazy-devices-test.js | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/lib/services/contextServer.js b/lib/services/contextServer.js index 58ef83125..d2005c2e4 100644 --- a/lib/services/contextServer.js +++ b/lib/services/contextServer.js @@ -223,7 +223,7 @@ function handleQuery(req, res, next) { } function completeAttributes(attributes, device, callback) { - if (attributes) { + if (attributes && attributes.length !== 0) { logger.debug(context, 'Handling received set of attributes: %j', attributes); callback(null, attributes); } else if (device.lazy) { diff --git a/test/unit/lazy-devices-test.js b/test/unit/lazy-devices-test.js index 0045d0d65..73ed70b50 100644 --- a/test/unit/lazy-devices-test.js +++ b/test/unit/lazy-devices-test.js @@ -450,6 +450,72 @@ describe('IoT Agent Lazy Devices', function() { }); }); + describe('When a query arrives to the IoT Agent with an empty attributes array', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', + method: 'POST', + json: { + entities: [ + { + type: 'Light', + isPattern: 'false', + id: 'light1' + } + ], + attributes: [] + } + }, + sensorData = [ + { + id: 'light1', + isPattern: false, + type: 'Light', + attributes: [ + { + name: 'temperature', + type: 'centigrades', + value: 19 + } + ] + } + ]; + + beforeEach(function(done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://10.11.128.16:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/NGSI9/registerContext', + utils.readExampleFile('./test/unit/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(200, + utils.readExampleFile('./test/unit/contextAvailabilityResponses/registerIoTAgent1Success.json')); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); + }); + + it('should return the information of all the attributes', function(done) { + var expectedResponse = utils + .readExampleFile('./test/unit/contextProviderResponses/queryInformationResponseEmptyAttributes.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, attributes, callback) { + should.exist(attributes); + attributes.length.should.equal(1); + attributes[0].should.equal('temperature'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); + describe('When a context query arrives to the IoT Agent for a type with static attributes', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/v1/queryContext', From 3ef82dc8fa3e4c7038469ae6d05967cf9c980491 Mon Sep 17 00:00:00 2001 From: Daniel Moran Date: Fri, 10 Apr 2015 14:16:46 +0200 Subject: [PATCH 78/78] Change to the new version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d036c994..e79db6a8d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "iotagent-node-lib", "description": "IoT Agent library to interface with NGSI Context Broker", - "version": "0.2.0-next", + "version": "0.3.0", "homepage": "https://github.com/telefonicaid/iotagent-node-lib", "keywords": [ "fiware",