diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index aa28dd515..5d91d22e0 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,3 +1,4 @@ +- Add: endpoit to send ngsiv2 measure to Context Broker - Fix: binary data representation when sending data through HTTP & MQTT (#690) - Fix: ensure service and subservice from device in logs about error proccesing message - Remove: legacy code about unused parsedMessageError flag diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index 0ef86551e..0b8a663fa 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -376,6 +376,53 @@ function handleIncomingMeasure(req, res, next) { iotaUtils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure); } +function handleIncomingNgsiv2Measure(req, res, next) { + function sendHandler(error) { + if (error) { + next(error); + config + .getLogger() + .error( + context, + 'MEASURES-002: Could not send the ngsiv2 measure to the Context Broker due to an error: %j', + error + ); + } else { + config + .getLogger() + .info( + context, + 'NGSIV2 measures for device %s with apiKey %s successfully sent', + req.deviceId, + req.apiKey + ); + + finishSouthBoundTransaction(next); + } + } + + function processNGSIv2MeasureWithDevice(device) { + iotAgentLib.sendNgsiv2Measure(device.name, device.type, '', req.jsonPayload, device, sendHandler); + } + + function processNGSIv2Measure(error, device) { + if (error) { + next(error); + } else { + const localContext = _.clone(context); + req.device = device; + localContext.service = device.service; + localContext.subservice = device.subservice; + intoTrans(localContext, processNGSIv2MeasureWithDevice)(device); + } + } + + context = fillService(context, { service: 'n/a', subservice: 'n/a' }); + config.getLogger().debug(context, 'Processing HTTP NGSIv2 measure'); + + iotAgentLib.getConfiguration(config.getConfig().iota.defaultResource || '', req.apiKey, processNGSIv2Measure); +} + function isCommand(req, res, next) { if ( req.path === @@ -633,6 +680,15 @@ function start(callback) { returnCommands ); + httpBindingServer.router.post( + constants.HTTP_NGSIV2_MEASURE_PATH, + bodyParser.json({ strict: false }), // accept anything JSON.parse accepts + checkMandatoryParams(false), + parseData, + handleIncomingNgsiv2Measure, + returnCommands + ); + httpBindingServer.router.post( (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + '/' + diff --git a/lib/constants.js b/lib/constants.js index f82a67d56..fa946293b 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -35,6 +35,7 @@ module.exports = { HTTP_MEASURE_PATH: '/iot/d', HTTP_CONFIGURATION_PATH: '/configuration', HTTP_COMMANDS_PATH: '/commands', + HTTP_NGSIV2_MEASURE_PATH: '/iot/ngsiv2', TIMESTAMP_ATTRIBUTE: 'TimeInstant', TIMESTAMP_TYPE_NGSI2: 'DateTime', diff --git a/package.json b/package.json index e60a6386e..ce13b4e2e 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "body-parser": "1.20.0", "dateformat": "3.0.3", "express": "4.18.1", - "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master", + "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#task/allow_ngsiv2_as_measure", "logops": "2.1.2", "mqtt": "4.3.7", "sinon": "~6.1.0", diff --git a/test/unit/ngsiv2/HTTP_receive_measures-test3.js b/test/unit/ngsiv2/HTTP_receive_measures-test3.js index 98b7efc86..95e9dd37a 100644 --- a/test/unit/ngsiv2/HTTP_receive_measures-test3.js +++ b/test/unit/ngsiv2/HTTP_receive_measures-test3.js @@ -43,8 +43,9 @@ const groupCreation = { resource: '/iot/json', apikey: 'KL223HHV8732SFL1', entity_type: 'TheLightType', - trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', - cbHost: 'http://unexistentHost:1026', + service: 'smartgondor', + subservice: 'gardens', + cbHost: 'http://192.168.1.1:1026', commands: [], lazy: [], attributes: [ @@ -138,7 +139,7 @@ describe('HTTP: Measure reception ', function () { }); }); - describe('When a POST single JSON measure with NGSIv2 format arrives for the HTTP binding', function () { + describe('When a POST single JSON measure for an attribute with NGSIv2 format arrives for the HTTP binding', function () { const optionsMeasure = { url: 'http://localhost:' + config.http.port + '/iot/json/', method: 'POST', @@ -228,7 +229,7 @@ describe('HTTP: Measure reception ', function () { }); }); - describe('When a POST single JSON measure with NGSILD format arrives for the HTTP binding', function () { + describe('When a POST single JSON measure for an attribute with NGSILD format arrives for the HTTP binding', function () { const optionsMeasure = { url: 'http://localhost:' + config.http.port + '/iot/json/', method: 'POST', @@ -305,6 +306,92 @@ describe('HTTP: Measure reception ', function () { }); }); + describe('When a POST with a NGSIv2 measure arrives for the HTTP binding', function () { + const optionsMeasure = { + url: 'http://localhost:' + config.http.port + '/iot/ngsiv2', + method: 'POST', + json: { + id: 'urn:ngsi-ld:Streetlight:Streetlight-Mylightpoint-2', + type: 'Streetlight', + name: { + type: 'Text', + value: 'MyLightPoint-test1' + }, + description: { + type: 'Text', + value: 'testdescription' + }, + status: { + type: 'Text', + value: 'connected' + }, + dateServiceStarted: { + type: 'DateTime', + value: '2020-06-04T09: 55: 02' + }, + locationComment: { + type: 'Text', + value: 'Test1' + }, + location: { + type: 'geo:json', + value: { + coordinates: [-87.88429, 41.99499], + type: 'Point' + } + }, + address: { + type: 'Text', + value: { + streetAddress: 'MyStreet' + } + }, + isRemotelyManaged: { + type: 'Integer', + value: 1 + }, + installationDate: { + type: 'DateTime', + value: '2022-04-17T02: 30: 04' + } + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + }, + qs: { + i: 'MQTT_2', + k: 'KL223HHV8732SFL1' + } + }; + + beforeEach(function (done) { + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/v2/op/update', utils.readExampleFile('./test/unit/ngsiv2/contextRequests/ngsiv2entities.json')) + .reply(204); + + request(groupCreation, function (error, response, body) { + done(); + }); + }); + it('should return a 200 OK with no error', function (done) { + request(optionsMeasure, function (error, result, body) { + should.not.exist(error); + result.statusCode.should.equal(200); + done(); + }); + }); + + it('should send its value to the Context Broker', function (done) { + request(optionsMeasure, function (error, result, body) { + contextBrokerMock.done(); + done(); + }); + }); + }); + describe('When a POST single Text measure arrives for the HTTP binding', function () { const optionsMeasure = { url: 'http://localhost:' + config.http.port + '/iot/json/attrs/humidity', diff --git a/test/unit/ngsiv2/contextRequests/ngsiv2entities.json b/test/unit/ngsiv2/contextRequests/ngsiv2entities.json new file mode 100644 index 000000000..4ef8b2d46 --- /dev/null +++ b/test/unit/ngsiv2/contextRequests/ngsiv2entities.json @@ -0,0 +1,54 @@ +{ + "actionType": "append", + "entities": [ + { + "id": "urn:ngsi-ld:Streetlight:Streetlight-Mylightpoint-2", + "type": "Streetlight", + "name": { + "type": "Text", + "value": "MyLightPoint-test1" + }, + "description": { + "type": "Text", + "value": "testdescription" + }, + "status": { + "type": "Text", + "value": "connected" + }, + "dateServiceStarted": { + "type": "DateTime", + "value": "2020-06-04T09: 55: 02" + }, + "locationComment": { + "type": "Text", + "value": "Test1" + }, + "location": { + "type": "geo:json", + "value": { + "coordinates": [ + -87.88429, + 41.99499 + ], + "type": "Point" + } + }, + "address": { + "type": "Text", + "value": { + "streetAddress": "MyStreet" + } + }, + "isRemotelyManaged": { + "type": "Integer", + "value": 1 + }, + "installationDate": { + "type": "DateTime", + "value": "2022-04-17T02: 30: 04" + } + } + ] +} +