From d2fd30184cb01954b6949b29c959b66bcfe574b7 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Tue, 3 Nov 2020 17:37:49 +0100 Subject: [PATCH 1/9] Add mixed mode --- doc/api.md | 10 +- doc/installationguide.md | 15 +- lib/commonConfig.js | 37 ++- lib/fiware-iotagent-lib.js | 3 +- lib/model/Device.js | 3 +- lib/model/Group.js | 3 +- lib/plugins/attributeAlias.js | 4 +- lib/plugins/bidirectionalData.js | 2 +- lib/plugins/expressionParser.js | 2 +- lib/plugins/expressionPlugin.js | 2 +- lib/plugins/jexlParser.js | 2 +- lib/plugins/multiEntity.js | 2 +- lib/plugins/pluginUtils.js | 7 +- lib/plugins/timestampProcessPlugin.js | 2 +- lib/services/common/iotManagerService.js | 3 +- lib/services/devices/deviceRegistryMongoDB.js | 61 ++-- lib/services/devices/deviceService.js | 35 ++- lib/services/devices/devices-NGSI-mixed.js | 65 ++++ lib/services/devices/registrationUtils.js | 15 +- lib/services/groups/groupRegistryMongoDB.js | 6 +- lib/services/ngsi/entities-NGSI-mixed.js | 65 ++++ lib/services/ngsi/ngsiService.js | 73 ++--- lib/services/ngsi/ngsiUtils.js | 2 +- lib/services/ngsi/subscription-NGSI-mixed.js | 62 ++++ lib/services/ngsi/subscriptionService.js | 18 +- .../northBound/contextServer-NGSI-mixed.js | 39 +++ lib/services/northBound/contextServer.js | 18 +- .../northBound/deviceProvisioningServer.js | 9 +- lib/templates/createDevice.json | 4 + lib/templates/createDeviceLax.json | 12 + lib/templates/deviceGroup.json | 4 + lib/templates/updateDevice.json | 4 + .../lazyAndCommands/polling-commands-test.js | 2 - .../provisioning/ngsi-versioning-test.js | 281 ++++++++++++++++++ 34 files changed, 731 insertions(+), 141 deletions(-) create mode 100644 lib/services/devices/devices-NGSI-mixed.js create mode 100644 lib/services/ngsi/entities-NGSI-mixed.js create mode 100644 lib/services/ngsi/subscription-NGSI-mixed.js create mode 100644 lib/services/northBound/contextServer-NGSI-mixed.js create mode 100644 test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js diff --git a/doc/api.md b/doc/api.md index 5f8673011..8611099cc 100644 --- a/doc/api.md +++ b/doc/api.md @@ -90,8 +90,9 @@ correspondence between the API resource fields and the same fields in the databa | `attributes` | `attributes` | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. | | `static_attributes` | `staticAttributes` | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. | | `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. | -| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | -| `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is false. | +| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. | +| `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is `false`. | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. ### Service Group Endpoint @@ -224,7 +225,12 @@ the API resource fields and the same fields in the database model. | `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | | `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | +<<<<<<< HEAD | `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is false. | `true/false` | +======= +| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld` | +>>>>>>> d272d82... Add mixed mode #### Attribute lists diff --git a/doc/installationguide.md b/doc/installationguide.md index 386487a8a..8a5fd85dc 100644 --- a/doc/installationguide.md +++ b/doc/installationguide.md @@ -20,7 +20,7 @@ These are the parameters that can be configured in the global section: } ``` -- If you want to use NGSI v2: +- If you want to use **NGSI v2**: ```javascript { @@ -30,7 +30,7 @@ These are the parameters that can be configured in the global section: } ``` -- If you want to use NGSI-LD (experimental): +- If you want to use **NGSI-LD** (experimental): ```javascript { @@ -41,6 +41,17 @@ These are the parameters that can be configured in the global section: } ``` +- If you want to support a "mixed" mode with both **NGSI-v2** and **NGSI-LD** (experimental): + +```javascript +{ + host: '192.168.56.101', + port: '1026', + ngsiVersion: 'mixed', + jsonLdContext: 'http://context.json-ld' // or ['http://context1.json-ld','http://context2.json-ld'] if you need more than one +} +``` + Where `http://context.json-ld` is the location of the NGSI-LD `@context` element which provides additional information allowing the computer to interpret the rest of the data with more clarity and depth. Read the [JSON-LD specification](https://w3c.github.io/json-ld-syntax/#the-context) for more informtaion. diff --git a/lib/commonConfig.js b/lib/commonConfig.js index 8a8a3373e..22b0563b7 100644 --- a/lib/commonConfig.js +++ b/lib/commonConfig.js @@ -504,30 +504,40 @@ function getCommandRegistry() { } /** - * It checks if the configuration file states the use of NGSIv2 + * Returns the supported NGSI format * - * @return {boolean} Result of the checking + * @return {string} the supported NGSI format */ -function checkNgsi2() { - if (config.contextBroker && config.contextBroker.ngsiVersion && config.contextBroker.ngsiVersion === 'v2') { - return true; +function ngsiVersion() { + if (config && config.contextBroker && config.contextBroker.ngsiVersion) { + return config.contextBroker.ngsiVersion.toLowerCase(); } - - return false; + return 'unknown'; } /** - * It checks if the configuration file states the use of NGSI-LD + * It checks if the configuration file states a non-legacy format, + * either v2, LD or mixed. * * @return {boolean} Result of the checking */ -function checkNgsiLD() { - if (config.contextBroker && config.contextBroker.ngsiVersion && config.contextBroker.ngsiVersion === 'ld') { - return true; +function isCurrentNgsi() { + if (config.contextBroker && config.contextBroker.ngsiVersion) { + const version = config.contextBroker.ngsiVersion.toLowerCase(); + return version === 'v2' || version === 'ld' || version === 'mixed'; } - return false; } +/** + * It checks if a combination of typeInformation or common Config is LD + * + * @return {boolean} Result of the checking + */ +function checkNgsiLD(typeInformation) { + const format = typeInformation.ngsiVersion || ngsiVersion(); + return format.toLowerCase() === 'ld'; +} + function setSecurityService(newSecurityService) { securityService = newSecurityService; } @@ -544,8 +554,9 @@ exports.setGroupRegistry = setGroupRegistry; exports.getGroupRegistry = getGroupRegistry; exports.setCommandRegistry = setCommandRegistry; exports.getCommandRegistry = getCommandRegistry; -exports.checkNgsi2 = checkNgsi2; +exports.ngsiVersion = ngsiVersion; exports.checkNgsiLD = checkNgsiLD; +exports.isCurrentNgsi = isCurrentNgsi; exports.setSecurityService = setSecurityService; exports.getSecurityService = getSecurityService; exports.getSecretData = getSecretData; diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js index 9e6f1ab07..da0c0f91f 100644 --- a/lib/fiware-iotagent-lib.js +++ b/lib/fiware-iotagent-lib.js @@ -27,7 +27,6 @@ const ngsi = require('./services/ngsi/ngsiService'); const intoTrans = require('./services/common/domain').intoTrans; const middlewares = require('./services/common/genericMiddleware'); const db = require('./model/dbConn'); -const ngsiService = require('./services/ngsi/ngsiService'); const subscriptions = require('./services/ngsi/subscriptionService'); const statsRegistry = require('./services/stats/statsRegistry'); const domainUtils = require('./services/common/domain'); @@ -179,7 +178,7 @@ function doActivate(newConfig, callback) { deviceService.init(); subscriptions.init(); contextServer.init(); - ngsiService.init(); + ngsi.init(); commands.start(); diff --git a/lib/model/Device.js b/lib/model/Device.js index 0090023fe..816ff9311 100644 --- a/lib/model/Device.js +++ b/lib/model/Device.js @@ -49,7 +49,8 @@ const Device = new Schema({ internalAttributes: Object, autoprovision: Boolean, expressionLanguage: String, - explicitAttrs: Boolean + explicitAttrs: Boolean, + ngsiVersion: String }); function load(db) { diff --git a/lib/model/Group.js b/lib/model/Group.js index de94694b2..fd4b95988 100644 --- a/lib/model/Group.js +++ b/lib/model/Group.js @@ -43,7 +43,8 @@ const Group = new Schema({ internalAttributes: Array, autoprovision: Boolean, expressionLanguage: String, - explicitAttrs: Boolean + explicitAttrs: Boolean, + ngsiVersion: String }); function load(db) { diff --git a/lib/plugins/attributeAlias.js b/lib/plugins/attributeAlias.js index 5f3501196..838206f13 100644 --- a/lib/plugins/attributeAlias.js +++ b/lib/plugins/attributeAlias.js @@ -73,7 +73,7 @@ function extractAllMappings(typeInformation) { function applyAlias(mappings) { return function aliasApplier(attribute) { if (mappings.direct[attribute.name]) { - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { /*jshint camelcase: false */ attribute.object_id = attribute.name; // inverse not usefull due to collision } @@ -93,7 +93,7 @@ function applyAlias(mappings) { */ function updateAttribute(entity, typeInformation, callback) { const mappings = extractAllMappings(typeInformation); - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { let attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity); attsArray = attsArray.map(applyAlias(mappings)); entity = utils.createNgsi2Entity(entity.id, entity.type, attsArray, true); diff --git a/lib/plugins/bidirectionalData.js b/lib/plugins/bidirectionalData.js index e7ea9a70c..c782b1377 100644 --- a/lib/plugins/bidirectionalData.js +++ b/lib/plugins/bidirectionalData.js @@ -130,7 +130,7 @@ function sendSubscriptions(device, attributeList, callback) { logger.debug(context, 'Sending bidirectionality subscriptions for device [%s]', device.id); - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { async.map(attributeList, sendSingleSubscriptionNgsi2, callback); } else { async.map(attributeList, sendSingleSubscriptionNgsi1, callback); diff --git a/lib/plugins/expressionParser.js b/lib/plugins/expressionParser.js index 9e5455030..d9137cf5a 100644 --- a/lib/plugins/expressionParser.js +++ b/lib/plugins/expressionParser.js @@ -176,7 +176,7 @@ function expressionApplier(context, typeInformation) { }; /*jshint camelcase: false */ - if ((config.checkNgsi2() || config.checkNgsiLD()) && attribute.object_id) { + if (config.isCurrentNgsi() && attribute.object_id) { newAttribute.object_id = attribute.object_id; } diff --git a/lib/plugins/expressionPlugin.js b/lib/plugins/expressionPlugin.js index 701b13a01..18852eb13 100644 --- a/lib/plugins/expressionPlugin.js +++ b/lib/plugins/expressionPlugin.js @@ -112,7 +112,7 @@ function update(entity, typeInformation, callback) { } try { - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { let attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity); attsArray = processEntityUpdateNgsi2(attsArray); entity = utils.createNgsi2Entity(entity.id, entity.type, attsArray, true); diff --git a/lib/plugins/jexlParser.js b/lib/plugins/jexlParser.js index 61de1036d..23146bdaa 100644 --- a/lib/plugins/jexlParser.js +++ b/lib/plugins/jexlParser.js @@ -119,7 +119,7 @@ function expressionApplier(context, typeInformation) { }; /*jshint camelcase: false */ - if (config.checkNgsi2() && attribute.object_id) { + if (config.isCurrentNgsi() && attribute.object_id) { newAttribute.object_id = attribute.object_id; } diff --git a/lib/plugins/multiEntity.js b/lib/plugins/multiEntity.js index ef036b232..255ff7f28 100644 --- a/lib/plugins/multiEntity.js +++ b/lib/plugins/multiEntity.js @@ -288,7 +288,7 @@ function updateAttributeNgsi2(entity, typeInformation, callback) { } function updateAttribute(entity, typeInformation, callback) { - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { updateAttributeNgsi2(entity, typeInformation, callback); } else { updateAttributeNgsi1(entity, typeInformation, callback); diff --git a/lib/plugins/pluginUtils.js b/lib/plugins/pluginUtils.js index 778d719fc..01a9b4170 100644 --- a/lib/plugins/pluginUtils.js +++ b/lib/plugins/pluginUtils.js @@ -102,8 +102,7 @@ function createProcessAttribute(fn, attributeType) { if (attribute.type && attribute.type === attributeType) { attribute.value = fn(attribute.value); } - - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { // This code is backwards compatible to process metadata in the older NGSIv1-style (array) // as well as supporting the newer NGSIv2-style (object). The redundant Array Check can be // therefore be removed if/when NGSIv1 support is removed from the library. @@ -149,7 +148,7 @@ function createUpdateFilter(fn, attributeType) { return entity; } - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { entity = processEntityUpdateNgsi2(entity); } else { entity.contextElements = entity.contextElements.map(processEntityUpdateNgsi1); @@ -184,7 +183,7 @@ function createQueryFilter(fn, attributeType) { return entity; } - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { entity = processEntityQueryNgsi2(entity); } else { entity.contextResponses = entity.contextResponses.map(processEntityQueryNgsi1); diff --git a/lib/plugins/timestampProcessPlugin.js b/lib/plugins/timestampProcessPlugin.js index d4e21a28c..8fd8d7842 100644 --- a/lib/plugins/timestampProcessPlugin.js +++ b/lib/plugins/timestampProcessPlugin.js @@ -130,7 +130,7 @@ function updatePluginNgsi1(entity, entityType, callback) { * @param {Object} entity NGSI Entity as it would have been sent before the plugin. */ function updatePlugin(entity, entityType, callback) { - if (config.checkNgsi2() || config.checkNgsiLD()) { + if (config.isCurrentNgsi()) { updatePluginNgsi2(entity, entityType, callback); } else { updatePluginNgsi1(entity, entityType, callback); diff --git a/lib/services/common/iotManagerService.js b/lib/services/common/iotManagerService.js index ad61a6108..dc0e4b9a4 100644 --- a/lib/services/common/iotManagerService.js +++ b/lib/services/common/iotManagerService.js @@ -56,7 +56,8 @@ function register(callback) { timestamp: service.timestamp, autoprovision: service.autoprovision, explicitAttrs: service.explicitAttrs, - expressionLanguage: service.expressionLanguage + expressionLanguage: service.expressionLanguage, + ngsiVersion: service.ngsiVersion }; } diff --git a/lib/services/devices/deviceRegistryMongoDB.js b/lib/services/devices/deviceRegistryMongoDB.js index 9912e856c..b5eea04ff 100644 --- a/lib/services/devices/deviceRegistryMongoDB.js +++ b/lib/services/devices/deviceRegistryMongoDB.js @@ -34,6 +34,32 @@ let context = { op: 'IoTAgentNGSI.MongoDBDeviceRegister' }; +const attributeList = [ + 'id', + 'type', + 'name', + 'service', + 'subservice', + 'lazy', + 'commands', + 'staticAttributes', + 'active', + 'registrationId', + 'internalId', + 'internalAttributes', + 'resource', + 'apikey', + 'protocol', + 'endpoint', + 'transport', + 'polling', + 'timestamp', + 'autoprovision', + 'explicitAttrs', + 'expressionLanguage', + 'ngsiVersion' +]; + /** * Generates a handler for the save device operations. The handler will take the customary error and the saved device * as the parameters (and pass the serialized DAO as the callback value). @@ -58,35 +84,11 @@ function saveDeviceHandler(callback) { * @param {Object} newDevice Device object to be stored */ function storeDevice(newDevice, callback) { - const deviceObj = new Device.model(); // eslint-disable-line new-cap - const attributeList = [ - 'id', - 'type', - 'name', - 'service', - 'subservice', - 'lazy', - 'commands', - 'staticAttributes', - 'active', - 'registrationId', - 'internalId', - 'internalAttributes', - 'resource', - 'apikey', - 'protocol', - 'endpoint', - 'transport', - 'polling', - 'timestamp', - 'autoprovision', - 'explicitAttrs', - 'expressionLanguage' - ]; - - for (let i = 0; i < attributeList.length; i++) { - deviceObj[attributeList[i]] = newDevice[attributeList[i]]; - } + /* eslint-disable-next-line new-cap */ + const deviceObj = new Device.model(); + attributeList.forEach((key) => { + deviceObj[key] = newDevice[key]; + }); // Ensure protocol is in newDevice if (!newDevice.protocol && config.getConfig().iotManager && config.getConfig().iotManager.protocol) { @@ -286,6 +288,7 @@ function update(device, callback) { data.name = device.name; data.type = device.type; data.explicitAttrs = device.explicitAttrs; + data.ngsiVersion = device.ngsiVersion; data.save(saveDeviceHandler(callback)); } diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index 8c829a4bc..c4afedd97 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -48,13 +48,20 @@ let deviceHandler; /** * Loads the correct device handler based on the current config. */ + function init() { - if (config.checkNgsiLD()) { - deviceHandler = require('./devices-NGSI-LD'); - } else if (config.checkNgsi2()) { - deviceHandler = require('./devices-NGSI-v2'); - } else { - deviceHandler = require('./devices-NGSI-v1'); + switch (config.ngsiVersion()) { + case 'ld': + deviceHandler = require('./devices-NGSI-LD'); + break; + case 'v2': + deviceHandler = require('./devices-NGSI-v2'); + break; + case 'mixed': + deviceHandler = require('./devices-NGSI-mixed'); + break; + default: + deviceHandler = require('./devices-NGSI-v1'); } } @@ -162,6 +169,10 @@ function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuratio if (configuration && configuration.cbHost) { deviceData.cbHost = configuration.cbHost; } + if (configuration && configuration.ngsiVersion) { + deviceData.ngsiVersion = configuration.ngsiVersion; + } + logger.debug(context, 'deviceData after merge with conf: %j', deviceData); callback(null, deviceData); } @@ -244,9 +255,15 @@ function registerDevice(deviceObj, callback) { } } + if (!deviceData.ngsiVersion) { + if (configuration && configuration.ngsiVersion) { + deviceData.ngsiVersion = configuration.ngsiVersion; + } + } + if (!deviceData.name) { deviceData.name = deviceData.type + ':' + deviceData.id; - if (config.checkNgsiLD()) { + if (config.checkNgsiLD(configuration)) { deviceData.name = 'urn:ngsi-ld:' + deviceData.type + ':' + deviceData.id; } logger.debug(context, 'Device name not found, falling back to deviceType:deviceId [%s]', deviceData.name); @@ -532,7 +549,9 @@ function findOrCreate(deviceId, group, callback) { if ('autoprovision' in group && group.autoprovision !== undefined) { newDevice.autoprovision = group.autoprovision; } - + if ('ngsiVersion' in group && group.ngsiVersion !== undefined) { + newDevice.ngsiVersion = group.ngsiVersion; + } registerDevice(newDevice, function (error, device) { callback(error, device, group); }); diff --git a/lib/services/devices/devices-NGSI-mixed.js b/lib/services/devices/devices-NGSI-mixed.js new file mode 100644 index 000000000..bb8e2fc73 --- /dev/null +++ b/lib/services/devices/devices-NGSI-mixed.js @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + * + * Modified by: Jason Fox - FIWARE Foundation + */ + +const config = require('../../commonConfig'); +const deviceHandlerLD = require('./devices-NGSI-LD'); +const deviceHandlerV2 = require('./devices-NGSI-v2'); + +/** + * Creates the initial entity representing the device in the Context Broker using both NGSI-LD and NGSI-v2 + * This is important mainly to allow the rest of the updateContext operations to be performed. + * + * @param {Object} deviceData Object containing all the deviceData needed to send the registration. + * @param {Object} newDevice Device object that will be stored in the database. + */ +function createInitialEntityNgsiMixed(deviceData, newDevice, callback) { + if (config.checkNgsiLD(deviceData)) { + deviceHandlerLD.createInitialEntity(deviceData, newDevice, callback); + } else { + deviceHandlerV2.createInitialEntity(deviceData, newDevice, callback); + } +} + +/** + * Updates the register of an existing device identified by the Id and Type in the Context Broker, and the internal + * registry. It uses both NGSI-LD and NGSI-v2 + * + * The device id and type are required fields for a registration updated. Only the following attributes will be + * updated: lazy, active and internalId. Any other change will be ignored. The registration for the lazy attributes + * of the updated entity will be updated if existing, and created if not. If new active attributes are created, + * the entity will be updated creating the new attributes. + * + * @param {Object} deviceObj Object with all the device information (mandatory). + */ +function updateRegisterDeviceNgsiMixed(deviceObj, callback) { + if (config.checkNgsiLD(deviceObj)) { + deviceHandlerLD.updateRegisterDevice(deviceObj, callback); + } else { + deviceHandlerV2.updateRegisterDevice(deviceObj, callback); + } +} + +exports.createInitialEntity = createInitialEntityNgsiMixed; +exports.updateRegisterDevice = updateRegisterDeviceNgsiMixed; diff --git a/lib/services/devices/registrationUtils.js b/lib/services/devices/registrationUtils.js index cb1573cb5..fa7c905d0 100644 --- a/lib/services/devices/registrationUtils.js +++ b/lib/services/devices/registrationUtils.js @@ -506,12 +506,15 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) { * @param {Object} deviceData Object containing all the deviceData needed to send the registration. */ function sendRegistrations(unregister, deviceData, callback) { - if (config.checkNgsiLD()) { - sendRegistrationsNgsiLD(unregister, deviceData, callback); - } else if (config.checkNgsi2()) { - sendRegistrationsNgsi2(unregister, deviceData, callback); - } else { - sendRegistrationsNgsi1(unregister, deviceData, callback); + switch (config.ngsiVersion()) { + case 'ld': + sendRegistrationsNgsiLD(unregister, deviceData, callback); + break; + case 'v2': + sendRegistrationsNgsi2(unregister, deviceData, callback); + break; + default: + sendRegistrationsNgsi1(unregister, deviceData, callback); } } diff --git a/lib/services/groups/groupRegistryMongoDB.js b/lib/services/groups/groupRegistryMongoDB.js index 325490b74..e816c9e3e 100644 --- a/lib/services/groups/groupRegistryMongoDB.js +++ b/lib/services/groups/groupRegistryMongoDB.js @@ -77,7 +77,8 @@ function createGroup(group, callback) { 'internalAttributes', 'autoprovision', 'explicitAttrs', - 'expressionLanguage' + 'expressionLanguage', + 'ngsiVersion' ]; for (let i = 0; i < attributeList.length; i++) { @@ -270,7 +271,8 @@ function update(id, body, callback) { 'staticAttributes', 'internalAttributes', 'explicitAttrs', - 'expressionLanguage' + 'expressionLanguage', + 'ngsiVersion' ]; for (let i = 0; i < attributes.length; i++) { diff --git a/lib/services/ngsi/entities-NGSI-mixed.js b/lib/services/ngsi/entities-NGSI-mixed.js new file mode 100644 index 000000000..17984a359 --- /dev/null +++ b/lib/services/ngsi/entities-NGSI-mixed.js @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + * + * Modified by: Jason Fox - FIWARE Foundation + */ + +const config = require('../../commonConfig'); +const entityHandlerLD = require('./entities-NGSI-LD'); +const entityHandlerV2 = require('./entities-NGSI-v2'); + +/** + * Makes a query to the Device's entity in the context broker using NGSI-LD, with the list + * of attributes given by the 'attributes' array. + * + * @param {String} entityName Name of the entity to query. + * @param {Array} attributes Attribute array containing the names of the attributes to query. + * @param {Object} typeInformation Configuration information for the device. + * @param {String} token User token to identify against the PEP Proxies (optional). + */ +function sendQueryValueNgsiMixed(entityName, attributes, typeInformation, token, callback) { + if (config.checkNgsiLD(typeInformation)) { + entityHandlerLD.sendQueryValue(entityName, attributes, typeInformation, token, callback); + } else { + entityHandlerV2.sendQueryValue(entityName, attributes, typeInformation, token, callback); + } +} + +/** + * Makes an update in the Device's entity in the context broker, with the values given + * in the 'attributes' array. This array should comply to the NGSI-LD or NGSI-v2 attribute format. + * + * @param {String} entityName Name of the entity 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). + */ +function sendUpdateValueNgsiMixed(entityName, attributes, typeInformation, token, callback) { + if (config.checkNgsiLD(typeInformation)) { + entityHandlerLD.sendUpdateValue(entityName, attributes, typeInformation, token, callback); + } else { + entityHandlerV2.sendUpdateValue(entityName, attributes, typeInformation, token, callback); + } +} + +exports.sendUpdateValue = sendUpdateValueNgsiMixed; +exports.sendQueryValue = sendQueryValueNgsiMixed; diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index 236fd61aa..c2fc33648 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -38,18 +38,25 @@ const context = { op: 'IoTAgentNGSI.NGSIService' }; +const attributeList = ['trust', 'cbHost', 'ngsiVersion']; let entityHandler; /** * Loads the correct ngsiService handler based on the current config. */ function init() { - if (config.checkNgsiLD()) { - entityHandler = require('./entities-NGSI-LD'); - } else if (config.checkNgsi2()) { - entityHandler = require('./entities-NGSI-v2'); - } else { - entityHandler = require('./entities-NGSI-v1'); + switch (config.ngsiVersion()) { + case 'ld': + entityHandler = require('./entities-NGSI-LD'); + break; + case 'v2': + entityHandler = require('./entities-NGSI-v2'); + break; + case 'mixed': + entityHandler = require('./entities-NGSI-mixed'); + break; + default: + entityHandler = require('./entities-NGSI-v1'); } } @@ -130,30 +137,22 @@ function executeWithDeviceInformation(operationFunction) { ); config.getGroupRegistry().getType(type, function (error, deviceGroup) { let typeInformation; + const configDeviceInfo = config.getConfig().types[type]; if (error) { logger.debug(context, 'error %j in get group device', error); } + + // For anonymous devices use the typeInformation from the provisioned group and/or config directly. + // For preregistered devices, augment the existing deviceInformation with selected attributes. if (!callback) { callback = deviceInformation; - - if (deviceGroup) { - typeInformation = deviceGroup; - } else { - typeInformation = config.getConfig().types[type]; - } + typeInformation = deviceGroup || configDeviceInfo; } else { typeInformation = deviceInformation; - if (!typeInformation.trust) { - if (deviceGroup && deviceGroup.trust) { - typeInformation.trust = deviceGroup.trust; - } else if (config.getConfig().types[type] && config.getConfig().types[type].trust) { - typeInformation.trust = config.getConfig().types[type].trust; - } - - if (deviceGroup && deviceGroup.cbHost) { - typeInformation.cbHost = deviceGroup.cbHost; - } - } + attributeList.forEach((key) => { + typeInformation[key] = + typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key]; + }); } if (config.getConfig().authentication && config.getConfig().authentication.enabled) { @@ -216,33 +215,19 @@ function setCommandResult( } ]; + // For anonymous devices use the typeInformation from the provisioned group and/or config directly. + // For preregistered devices, augment the existing deviceInformation with selected attributes. if (!callback) { callback = deviceInformation; - - if (deviceGroup) { - typeInformation = deviceGroup; - } else { - typeInformation = config.getConfig().types[resource]; - } + typeInformation = deviceGroup || config.getConfig().types[resource]; } else { typeInformation = deviceInformation; } - if (!typeInformation.type) { - if (deviceGroup) { - typeInformation.type = deviceGroup.type; - } else { - typeInformation.type = resource; - } - } - - if (!typeInformation.service) { - typeInformation.service = config.getConfig().service; - } - - if (!typeInformation.subservice) { - typeInformation.subservice = config.getConfig().subservice; - } + // Ensure type, servce and subservice are always set, using fallbacks as necessary. + typeInformation.type = typeInformation.type || (deviceGroup || {}).type || resource; + typeInformation.service = typeInformation.service || config.getConfig().service; + typeInformation.subservice = typeInformation.subservice || config.getConfig().subservice; commandInfo = _.where(typeInformation.commands, { name: commandName }); diff --git a/lib/services/ngsi/ngsiUtils.js b/lib/services/ngsi/ngsiUtils.js index 9005fbab2..8b33a2dd2 100644 --- a/lib/services/ngsi/ngsiUtils.js +++ b/lib/services/ngsi/ngsiUtils.js @@ -126,7 +126,7 @@ function createRequestObject(url, typeInformation, token) { } } - if (config.checkNgsiLD()) { + if (config.checkNgsiLD(typeInformation)) { headers['Content-Type'] = 'application/ld+json'; headers['NGSILD-Tenant'] = headers['fiware-service']; headers['NGSILD-Path'] = headers['fiware-servicepath']; diff --git a/lib/services/ngsi/subscription-NGSI-mixed.js b/lib/services/ngsi/subscription-NGSI-mixed.js new file mode 100644 index 000000000..4e0b4ed4f --- /dev/null +++ b/lib/services/ngsi/subscription-NGSI-mixed.js @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + * + * Modified by: Jason Fox - FIWARE Foundation + */ + +const config = require('../../commonConfig'); +const subscriptionHandlerLD = require('./subscription-NGSI-LD'); +const subscriptionHandlerV2 = require('./subscription-NGSI-v2'); + +/** + * Makes a subscription for the given device's entity using NGSI-LD, triggered by the given attributes. + * The contents of the notification can be selected using the "content" array (that can be left blank + * to notify the complete entity). + * + * @param {Object} device Object containing all the information about a particular device. + * @param {Object} triggers Array with the names of the attributes that would trigger the subscription + * @param {Object} content Array with the names of the attributes to retrieve in the notification. + */ +function subscribeNgsiMixed(device, triggers, content, callback) { + if (config.checkNgsiLD(device)) { + subscriptionHandlerLD.subscribe(device, triggers, content, callback); + } else { + subscriptionHandlerV2.subscribe(device, triggers, content, callback); + } +} + +/** + * Remove the subscription with the given ID from the Context Broker and from the device repository using NGSI-LD. + * + * @param {Object} device Object containing all the information about a particular device. + * @param {String} id ID of the subscription to remove. + */ +function unsubscribeNgsiMixed(device, id, callback) { + if (config.checkNgsiLD(device)) { + subscriptionHandlerLD.unsubscribe(device, id, callback); + } else { + subscriptionHandlerV2.unsubscribe(device, id, callback); + } +} + +exports.subscribe = subscribeNgsiMixed; +exports.unsubscribe = unsubscribeNgsiMixed; diff --git a/lib/services/ngsi/subscriptionService.js b/lib/services/ngsi/subscriptionService.js index 46ac49fa4..536b2d7e0 100644 --- a/lib/services/ngsi/subscriptionService.js +++ b/lib/services/ngsi/subscriptionService.js @@ -36,12 +36,18 @@ let subscriptionHandler; * Loads the correct subscription handler based on the current config. */ function init() { - if (config.checkNgsiLD()) { - subscriptionHandler = require('./subscription-NGSI-LD'); - } else if (config.checkNgsi2()) { - subscriptionHandler = require('./subscription-NGSI-v2'); - } else { - subscriptionHandler = require('./subscription-NGSI-v1'); + switch (config.ngsiVersion()) { + case 'ld': + subscriptionHandler = require('./subscription-NGSI-LD'); + break; + case 'v2': + subscriptionHandler = require('./subscription-NGSI-v2'); + break; + case 'mixed': + subscriptionHandler = require('./subscription-NGSI-mixed'); + break; + default: + subscriptionHandler = require('./subscription-NGSI-v1'); } } diff --git a/lib/services/northBound/contextServer-NGSI-mixed.js b/lib/services/northBound/contextServer-NGSI-mixed.js new file mode 100644 index 000000000..d14f909b3 --- /dev/null +++ b/lib/services/northBound/contextServer-NGSI-mixed.js @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::daniel.moranjimenez@telefonica.com + * + * Modified by: Jason Fox - FIWARE Foundation + */ + +const contextServerHandlerLD = require('./contextServer-NGSI-LD'); +const contextServerHandlerV2 = require('./contextServer-NGSI-v2'); + +/** + * Load the routes related to context dispatching (NGSI10 calls) for both v2 and LD (mixed mode) + * + * @param {Object} router Express request router object. + */ +function loadContextRoutesMixed(router) { + contextServerHandlerLD.loadContextRoutes(router); + contextServerHandlerV2.loadContextRoutes(router); +} + +exports.loadContextRoutes = loadContextRoutesMixed; diff --git a/lib/services/northBound/contextServer.js b/lib/services/northBound/contextServer.js index b1a578e28..a5c4534ba 100644 --- a/lib/services/northBound/contextServer.js +++ b/lib/services/northBound/contextServer.js @@ -37,12 +37,18 @@ let contextServerHandler; * Loads the correct context server handler based on the current config. */ function init() { - if (config.checkNgsiLD()) { - contextServerHandler = require('./contextServer-NGSI-LD'); - } else if (config.checkNgsi2()) { - contextServerHandler = require('./contextServer-NGSI-v2'); - } else { - contextServerHandler = require('./contextServer-NGSI-v1'); + switch (config.ngsiVersion()) { + case 'ld': + contextServerHandler = require('./contextServer-NGSI-LD'); + break; + case 'v2': + contextServerHandler = require('./contextServer-NGSI-v2'); + break; + case 'mixed': + contextServerHandler = require('./contextServer-NGSI-mixed'); + break; + default: + contextServerHandler = require('./contextServer-NGSI-v1'); } } diff --git a/lib/services/northBound/deviceProvisioningServer.js b/lib/services/northBound/deviceProvisioningServer.js index 7b9248edb..9b8b88034 100644 --- a/lib/services/northBound/deviceProvisioningServer.js +++ b/lib/services/northBound/deviceProvisioningServer.js @@ -59,7 +59,8 @@ const provisioningAPITranslation = { static_attributes: 'staticAttributes', autoprovision: 'autoprovision', explicitAttrs: 'explicitAttrs', - expressionLanguage: 'expressionLanguage' + expressionLanguage: 'expressionLanguage', + ngsiVersion: 'ngsiVersion' }; /** @@ -137,7 +138,8 @@ function handleProvision(req, res, next) { internalId: null, autoprovision: body.autoprovision, explicitAttrs: body.explicitAttrs, - expressionLanguage: body.expressionLanguage + expressionLanguage: body.expressionLanguage, + ngsiVersion: body.ngsiVersion }); } @@ -207,8 +209,9 @@ function toProvisioningAPIFormat(device) { internal_attributes: device.internalAttributes, protocol: device.protocol, autoprovision: device.autoprovision, + expressionLanguage: device.expressionLanguage, explicitAttrs: device.explicitAttrs, - expressionLanguage: device.expressionLanguage + ngsiVersion: device.ngsiVersion }; } diff --git a/lib/templates/createDevice.json b/lib/templates/createDevice.json index a89d33916..26fe72e72 100644 --- a/lib/templates/createDevice.json +++ b/lib/templates/createDevice.json @@ -44,6 +44,10 @@ "description": "Flag about only provisioned attributes will be processed to Context Broker", "type": "boolean" }, + "ngsiVersion": { + "description": "NGSI Interface for this device", + "type": "string" + }, "lazy": { "description": "list of lazy attributes of the devices", "type": "array", diff --git a/lib/templates/createDeviceLax.json b/lib/templates/createDeviceLax.json index 1920cb761..64d25efda 100644 --- a/lib/templates/createDeviceLax.json +++ b/lib/templates/createDeviceLax.json @@ -36,6 +36,18 @@ "description": "Transport protocol used by the platform to communicate with the device", "type": "string" }, + "expressionLanguage": { + "description": "Expression language used to apply expressions for this device", + "type": "boolean" + }, + "explicitAttrs": { + "description": "Flag about only provisioned attributes will be processed to Context Broker", + "type": "boolean" + }, + "ngsiVersion": { + "description": "NGSI Interface for this device", + "type": "string" + }, "lazy": { "description": "list of lazy attributes of the devices", "type": "array", diff --git a/lib/templates/deviceGroup.json b/lib/templates/deviceGroup.json index 8c9279cdb..fcf63dc11 100644 --- a/lib/templates/deviceGroup.json +++ b/lib/templates/deviceGroup.json @@ -41,6 +41,10 @@ "description": "Expression language used to for the group of devices", "type": "string" }, + "ngsiVersion": { + "description": "NGSI Interface for this group of devices", + "type": "string" + }, "attributes": { "description": "list of active attributes of the devices", "type": "array" diff --git a/lib/templates/updateDevice.json b/lib/templates/updateDevice.json index 46d7bf338..d3fd7b116 100644 --- a/lib/templates/updateDevice.json +++ b/lib/templates/updateDevice.json @@ -179,6 +179,10 @@ "explicitAttrs": { "description": "Flag to decide update of active attributes only", "type": "Boolean" + }, + "ngsiVersion": { + "description": "NGSI Interface for this device", + "type": "string" } } } diff --git a/test/unit/lazyAndCommands/polling-commands-test.js b/test/unit/lazyAndCommands/polling-commands-test.js index d596e81c5..2672138b3 100644 --- a/test/unit/lazyAndCommands/polling-commands-test.js +++ b/test/unit/lazyAndCommands/polling-commands-test.js @@ -122,9 +122,7 @@ const device3 = { describe('NGSI-v1 - Polling commands', function () { beforeEach(function (done) { logger.setLevel('FATAL'); - nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartGondor') .matchHeader('fiware-servicepath', 'gardens') diff --git a/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js b/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js new file mode 100644 index 000000000..0366dd870 --- /dev/null +++ b/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js @@ -0,0 +1,281 @@ +/* + * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of fiware-iotagent-lib + * + * fiware-iotagent-lib is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * fiware-iotagent-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with fiware-iotagent-lib. + * If not, see http://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Daniel Calvo - ATOS Research & Innovation + */ + +/* eslint-disable no-unused-vars */ + +const iotAgentLib = require('../../../../lib/fiware-iotagent-lib'); +const request = require('request'); +const should = require('should'); +const iotAgentConfig = { + logLevel: 'FATAL', + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'mixed' + }, + server: { + name: 'testAgent', + port: 4041, + baseRoot: '/' + }, + types: {}, + deviceRegistry: { + type: 'mongodb' + }, + mongodb: { + host: 'localhost', + port: '27017', + db: 'iotagent' + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M' +}; +const mongo = require('mongodb').MongoClient; +const mongoUtils = require('../../mongodb/mongoDBUtils'); +const optionsCreationDefault = { + url: 'http://localhost:4041/iot/services', + method: 'POST', + json: { + services: [ + { + apikey: 'default-test', + cbroker: 'http://orion:1026', + entity_type: 'Device', + resource: '/iot/default', + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Property' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } +}; +const optionsCreationV2 = { + url: 'http://localhost:4041/iot/services', + method: 'POST', + json: { + services: [ + { + apikey: 'v2-test', + cbroker: 'http://orion:1026', + ngsiVersion: 'v2', + entity_type: 'Device', + resource: '/iot/v2', + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Property' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } +}; + +const optionsCreationLD = { + url: 'http://localhost:4041/iot/services', + method: 'POST', + json: { + services: [ + { + apikey: 'ld-test', + cbroker: 'http://orion:1026', + entity_type: 'Device', + ngsiVersion: 'ld', + resource: '/iot/ld', + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Property' + } + ] + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } +}; + +const deviceCreationV2 = { + url: 'http://localhost:4041/iot/devices', + method: 'POST', + json: { + devices: [ + { + device_id: 'light2', + entity_name: 'light2', + entity_type: 'Device', + ngsiVersion: 'v2' + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } +}; +let iotAgentDb; +const nock = require('nock'); +let contextBrokerMock; + +describe('Mixed Mode: ngsiVersion test', function () { + const values = [ + { + name: 's', + type: 'Property', + value: true + } + ]; + + beforeEach(function (done) { + mongoUtils.cleanDbs(function () { + iotAgentLib.activate(iotAgentConfig, function () { + mongo.connect('mongodb://localhost:27017/iotagent', { useNewUrlParser: true }, function (err, db) { + iotAgentDb = db; + done(); + }); + }); + }); + }); + + afterEach(function (done) { + iotAgentLib.deactivate(function () { + iotAgentDb.close(function (error) { + mongoUtils.cleanDbs(done); + }); + }); + }); + describe('When a new default device group is provisioned', function () { + beforeEach(function (done) { + nock.cleanAll(); + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .post('/v2/entities/light1/attrs') + .query({ type: 'Device' }) + .reply(204); + + request(optionsCreationDefault, function (error, response, body) { + done(); + }); + }); + it('should operate using NGSI-v2', function (done) { + iotAgentLib.update('light1', 'Device', 'default-test', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('When a new v2 device group is provisioned', function () { + beforeEach(function (done) { + nock.cleanAll(); + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .post('/v2/entities/light1/attrs') + .query({ type: 'Device' }) + .reply(204); + + request(optionsCreationV2, function (error, response, body) { + done(); + }); + }); + it('should operate using NGSI-v2', function (done) { + iotAgentLib.update('light1', 'Device', 'v2-test', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('When an NGSI-LD device group is provisioned', function () { + beforeEach(function (done) { + nock.cleanAll(); + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('NGSILD-Tenant', 'smartGondor') + .post('/ngsi-ld/v1/entityOperations/upsert/') + .reply(204); + request(optionsCreationLD, function (error, response, body) { + done(); + }); + }); + it('should operate using NGSI-LD', function (done) { + iotAgentLib.update('light1', 'Device', 'ld-test', values, function (error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('When a new NGSI-LD device group is provisioned and overridden', function () { + beforeEach(function (done) { + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .post('/v2/entities/light2/attrs') + .query({ type: 'Device' }) + .reply(204); + request(optionsCreationLD, function (error, response, body) { + request(deviceCreationV2, function (error, response, body) { + done(); + }); + }); + }); + it('should operate using NGSI-v2', function (done) { + iotAgentLib.update('light2', 'Device', 'v2-test', values, { ngsiVersion: 'v2', type: 'Device' }, function ( + error + ) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); +}); From 4ed3e789bd9c2e772daa5d00587c24f63876d61b Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Thu, 26 Nov 2020 16:29:28 +0100 Subject: [PATCH 2/9] Remove merge artifact. --- doc/api.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/api.md b/doc/api.md index 8611099cc..e1a294552 100644 --- a/doc/api.md +++ b/doc/api.md @@ -225,12 +225,9 @@ the API resource fields and the same fields in the database model. | `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | | `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | -<<<<<<< HEAD -| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is false. | `true/false` | -======= + | `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld` | ->>>>>>> d272d82... Add mixed mode +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld/mixed` | #### Attribute lists From 9ad9268e96d58cf958e4ed15a42badf7bf6efe7d Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 10:49:06 +0100 Subject: [PATCH 3/9] Update api.md --- doc/api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 40877bc07..7f16a8e43 100644 --- a/doc/api.md +++ b/doc/api.md @@ -225,7 +225,6 @@ the API resource fields and the same fields in the database model. | `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | | `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | - | `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | | `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld/mixed` | From b0c8356fc133fb40e1f51dbd59fc17221e806e32 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 10:52:04 +0100 Subject: [PATCH 4/9] Update api.md --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 7f16a8e43..ec8f221d1 100644 --- a/doc/api.md +++ b/doc/api.md @@ -92,7 +92,7 @@ correspondence between the API resource fields and the same fields in the databa | `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. | | `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is `false`. | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v1`, `v2` or `ld`. The default is `v2`. ### Service Group Endpoint From 40b5256bab657a32dcacd1a3970c28c4255b2db1 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 10:55:01 +0100 Subject: [PATCH 5/9] Update api.md --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index ec8f221d1..2c85bed55 100644 --- a/doc/api.md +++ b/doc/api.md @@ -92,7 +92,7 @@ correspondence between the API resource fields and the same fields in the databa | `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. | | `explicitAttrs` | `explicitAttrs` | optional boolean value, to support selective ignore of measures so that IOTA doesn’t progress. If not specified default is `false`. | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v1`, `v2` or `ld`. The default is `v2`. +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. ### Service Group Endpoint From 52cb08904388963612a7715c6444a6836338d852 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 11:16:06 +0100 Subject: [PATCH 6/9] Update installationguide.md --- doc/installationguide.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/installationguide.md b/doc/installationguide.md index 8a5fd85dc..7a0a664ad 100644 --- a/doc/installationguide.md +++ b/doc/installationguide.md @@ -41,6 +41,10 @@ These are the parameters that can be configured in the global section: } ``` +Where `http://context.json-ld` is the location of the NGSI-LD `@context` element which provides additional information +allowing the computer to interpret the rest of the data with more clarity and depth. Read the +[JSON-LD specification](https://w3c.github.io/json-ld-syntax/#the-context) for more information. + - If you want to support a "mixed" mode with both **NGSI-v2** and **NGSI-LD** (experimental): ```javascript @@ -52,9 +56,9 @@ These are the parameters that can be configured in the global section: } ``` -Where `http://context.json-ld` is the location of the NGSI-LD `@context` element which provides additional information -allowing the computer to interpret the rest of the data with more clarity and depth. Read the -[JSON-LD specification](https://w3c.github.io/json-ld-syntax/#the-context) for more informtaion. +Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also be switched +to **NGSI LD** at service group or device provisioning time using the `ngsiVersion` field in the provisioning API. +The `ngsiVersion` field switch may be added at either group or device level, with the device level overriding the group setting. - **server**: configuration used to create the Context Server (port where the IoT Agent will be listening as a Context Provider and base root to prefix all the paths). The `port` attribute is required. If no `baseRoot` attribute is From e142485138bce5afcf2786ff2be48d2c7740b70b Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 11:25:15 +0100 Subject: [PATCH 7/9] Update api.md --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 2c85bed55..55ebf7160 100644 --- a/doc/api.md +++ b/doc/api.md @@ -226,7 +226,7 @@ the API resource fields and the same fields in the database model. | `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | | `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld/mixed` | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld`. | #### Attribute lists From 1a4d4fd01759b792a3ad702a4f6699be5e9a9497 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 11:56:21 +0100 Subject: [PATCH 8/9] Update api.md --- doc/api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api.md b/doc/api.md index 55ebf7160..a070a274b 100644 --- a/doc/api.md +++ b/doc/api.md @@ -225,8 +225,8 @@ the API resource fields and the same fields in the database model. | `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes | | `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` | | `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. | -| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | -| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD**payloads. The default is `v2`. | `v2/ld`. | +| `explicitAttrs` | `explicitAttrs` | Boolean value to support selective ignore of measures for device so that IOTA doesn’t progress. If not specified default is `false`. | `true/false` | +| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. | `v2/ld` | #### Attribute lists From 3dee480d64ca585edda45d817d247334945be431 Mon Sep 17 00:00:00 2001 From: Jason Fox Date: Mon, 30 Nov 2020 18:21:55 +0100 Subject: [PATCH 9/9] Update CNR --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index fed877c7d..8183459e1 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -6,6 +6,7 @@ Add basic NGSI-LD support as experimental feature (#842) - Multi-measures - Lazy Attributes - Commands +- Mixed mode (based in ngsiVersion field in the provisioning API) Update codebase to use ES6 - Remove JSHint and jshint overrides - Add esLint using standard tamia presets