iotagent-node-lib 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/doc/api.md +178 -69
  2. package/docker/Mosquitto/Dockerfile +1 -1
  3. package/lib/commonConfig.js +18 -0
  4. package/lib/errors.js +48 -20
  5. package/lib/services/commands/commandRegistryMemory.js +12 -2
  6. package/lib/services/commands/commandRegistryMongoDB.js +26 -15
  7. package/lib/services/devices/deviceRegistryMemory.js +1 -1
  8. package/lib/services/devices/deviceRegistryMongoDB.js +7 -12
  9. package/lib/services/devices/deviceService.js +4 -3
  10. package/lib/services/devices/devices-NGSI-LD.js +3 -3
  11. package/lib/services/devices/devices-NGSI-v2.js +10 -4
  12. package/lib/services/groups/groupRegistryMemory.js +1 -1
  13. package/lib/services/groups/groupRegistryMongoDB.js +1 -1
  14. package/lib/services/groups/groupService.js +9 -3
  15. package/lib/services/ngsi/entities-NGSI-LD.js +15 -7
  16. package/lib/services/ngsi/entities-NGSI-v2.js +86 -54
  17. package/lib/services/ngsi/ngsiService.js +3 -3
  18. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -0
  19. package/lib/services/ngsi/subscription-NGSI-v2.js +2 -0
  20. package/lib/services/northBound/contextServer-NGSI-LD.js +50 -55
  21. package/lib/services/northBound/contextServer-NGSI-v2.js +1 -1
  22. package/lib/services/northBound/deviceProvisioningServer.js +100 -26
  23. package/lib/services/northBound/restUtils.js +1 -1
  24. package/package.json +2 -2
  25. package/scripts/legacy_expression_tool/requirements.txt +1 -1
  26. package/test/functional/testCases.js +797 -66
  27. package/test/functional/testUtils.js +3 -1
  28. package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +0 -1
  29. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +8 -5
  30. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +34 -2
  31. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +37 -2
@@ -48,41 +48,36 @@ const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entit
48
48
  const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
49
49
  const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
50
50
 
51
-
52
51
  /**
53
52
  * Replacement of NGSI-LD Null placeholders with real null values
54
53
  *
55
54
  */
56
- function replaceNGSILDNull(payload){
57
- Object.keys(payload).forEach((key) =>{
55
+ function replaceNGSILDNull(payload) {
56
+ Object.keys(payload).forEach((key) => {
58
57
  const value = payload[key];
59
- if ( value === constants.NGSI_LD_NULL){
60
- payload[key] = null;
61
- } else if (typeof value === 'object' &&
62
- !Array.isArray(value) &&
63
- value !== null){
58
+ if (value === constants.NGSI_LD_NULL) {
59
+ payload[key] = null;
60
+ } else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
64
61
  payload[key] = replaceNGSILDNull(payload[key]);
65
62
  }
66
- })
67
- return payload;
63
+ });
64
+ return payload;
68
65
  }
69
66
 
70
67
  /**
71
68
  * Check to see if the payload or its subattributes contain null values
72
69
  *
73
70
  */
74
- function containsNulls(payload, result){
75
- Object.keys(payload).forEach((key) =>{
71
+ function containsNulls(payload, result) {
72
+ Object.keys(payload).forEach((key) => {
76
73
  const value = payload[key];
77
- if ( value === null){
78
- result.nulls = true;
79
- } else if (typeof value === 'object' &&
80
- !Array.isArray(value) &&
81
- value !== null){
74
+ if (value === null) {
75
+ result.nulls = true;
76
+ } else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
82
77
  containsNulls(payload[key], result);
83
78
  }
84
- })
85
- return result;
79
+ });
80
+ return result;
86
81
  }
87
82
 
88
83
  /**
@@ -90,23 +85,23 @@ function containsNulls(payload, result){
90
85
  * to real nulls and checks for the presence of null and datasetId
91
86
  *
92
87
  */
93
- function preprocessNGSILD(req, res, next){
94
- res.locals.hasDatasetId = false;
95
- const payload = req.body
96
- if (payload && typeof payload === 'object'){
97
- Object.keys(payload).forEach((key) =>{
98
- if (_.isArray(payload[key])){
88
+ function preprocessNGSILD(req, res, next) {
89
+ res.locals.hasDatasetId = false;
90
+ const payload = req.body;
91
+ if (payload && typeof payload === 'object') {
92
+ Object.keys(payload).forEach((key) => {
93
+ if (_.isArray(payload[key])) {
99
94
  payload[key].forEach((obj) => {
100
- if (obj.datasetId){
95
+ if (obj.datasetId) {
101
96
  res.locals.hasDatasetId = true;
102
97
  }
103
98
  });
104
- } else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none'){
105
- res.locals.hasDatasetId = true;
106
- }
99
+ } else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none') {
100
+ res.locals.hasDatasetId = true;
101
+ }
107
102
  });
108
103
  req.body = replaceNGSILDNull(payload);
109
- const result = { nulls: false }
104
+ const result = { nulls: false };
110
105
  containsNulls(payload, result);
111
106
  res.locals.hasNulls = result.nulls;
112
107
  }
@@ -124,16 +119,23 @@ function preprocessNGSILD(req, res, next){
124
119
  function validateNGSILD(supportNull, supportDatasetId) {
125
120
  return function validate(req, res, next) {
126
121
  if (!supportNull && res.locals.hasNulls) {
127
- next(new errors.BadRequest('NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'));
122
+ next(
123
+ new errors.BadRequest(
124
+ 'NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'
125
+ )
126
+ );
128
127
  } else if (!supportDatasetId && res.locals.hasDatasetId) {
129
- next(new errors.BadRequest('datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'));
128
+ next(
129
+ new errors.BadRequest(
130
+ 'datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'
131
+ )
132
+ );
130
133
  } else {
131
134
  next();
132
135
  }
133
136
  };
134
137
  }
135
138
 
136
-
137
139
  /**
138
140
  * Extract metadata attributes from input.
139
141
  *
@@ -413,34 +415,31 @@ function defaultQueryHandlerNgsiLD(id, type, service, subservice, attributes, ca
413
415
  * @param {Object} req Update request to generate Actions from
414
416
  */
415
417
  function generateMergePatchActionNgsiLD(req, callback) {
416
-
417
418
  const entityId = req.params.entity;
418
419
 
419
-
420
- function addAttributes(deviceData, body, attributes){
420
+ function addAttributes(deviceData, body, attributes) {
421
421
  const keys = Object.keys(body);
422
422
 
423
423
  for (const j in deviceData) {
424
424
  if (keys.includes(deviceData[j].name)) {
425
- const obj = body[deviceData[j].name]
426
- if ( obj === null) {
425
+ const obj = body[deviceData[j].name];
426
+ if (obj === null) {
427
427
  attributes.push({
428
428
  type: deviceData[j].type,
429
429
  value: null,
430
430
  name: deviceData[j].name
431
431
  });
432
432
  } else {
433
- attributes.push({
433
+ attributes.push({
434
434
  type: deviceData[j].type,
435
435
  value: obj.value,
436
436
  name: deviceData[j].name
437
437
  });
438
438
  }
439
- }
439
+ }
440
440
  }
441
441
  return attributes;
442
442
  }
443
-
444
443
 
445
444
  deviceService.getDeviceByName(
446
445
  entityId,
@@ -451,8 +450,8 @@ function generateMergePatchActionNgsiLD(req, callback) {
451
450
  callback(error);
452
451
  } else {
453
452
  const attributes = [];
454
- addAttributes(deviceObj.commands, req.body, attributes)
455
- addAttributes(deviceObj.lazy, req.body, attributes)
453
+ addAttributes(deviceObj.commands, req.body, attributes);
454
+ addAttributes(deviceObj.lazy, req.body, attributes);
456
455
  const executeMergePatchHandler = apply(
457
456
  contextServerUtils.mergePatchHandler,
458
457
  entityId,
@@ -461,10 +460,7 @@ function generateMergePatchActionNgsiLD(req, callback) {
461
460
  contextServerUtils.getLDPath(req),
462
461
  attributes
463
462
  );
464
- async.waterfall(
465
- [executeMergePatchHandler],
466
- callback()
467
- );
463
+ async.waterfall([executeMergePatchHandler], callback());
468
464
  }
469
465
  }
470
466
  );
@@ -477,7 +473,6 @@ function generateMergePatchActionNgsiLD(req, callback) {
477
473
  * @param {Object} res Response that will be sent.
478
474
  */
479
475
  function handleMergePatchNgsiLD(req, res, next) {
480
-
481
476
  function handleMergePatchRequest(error, result) {
482
477
  if (error) {
483
478
  logger.debug(context, 'There was an error handling the merge-patch: %s.', error);
@@ -492,15 +487,15 @@ function handleMergePatchNgsiLD(req, res, next) {
492
487
  if ((req.is('json') || req.is('application/ld+json')) === false) {
493
488
  return handleMergePatchRequest(new errors.UnsupportedContentType(req.header('content-type')));
494
489
  }
495
-
490
+
496
491
  if (req.body) {
497
492
  logger.debug(context, JSON.stringify(req.body, null, 4));
498
493
  }
499
494
 
500
- if (contextServerUtils.mergePatchHandler){
495
+ if (contextServerUtils.mergePatchHandler) {
501
496
  generateMergePatchActionNgsiLD(req, handleMergePatchRequest);
502
497
  } else {
503
- return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path))
498
+ return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path));
504
499
  }
505
500
  }
506
501
 
@@ -610,7 +605,7 @@ function handleQueryNgsiLD(req, res, next) {
610
605
  getFunction(function handleFindDevice(error, innerDevice) {
611
606
  let deviceList = [];
612
607
  if (!innerDevice) {
613
- return callback(new errors.DeviceNotFound(contextEntity.id));
608
+ return callback(new errors.DeviceNotFound(contextEntity.id, contextEntity));
614
609
  }
615
610
 
616
611
  if (innerDevice.count) {
@@ -689,7 +684,7 @@ function ErrorHandlingNgsiLD(action) {
689
684
  error: error.name,
690
685
  description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
691
686
  });
692
- }
687
+ };
693
688
  }
694
689
 
695
690
  /**
@@ -812,7 +807,7 @@ function loadUnsupportedEndpointsNGSILD(router) {
812
807
  function loadContextRoutesNGSILD(router) {
813
808
  // In a more evolved implementation, more endpoints could be added to queryPathsNgsi2
814
809
  // according to https://www.etsi.org/standards-search#page=1&search=GS%20CIM%20009
815
-
810
+
816
811
  const support = config.getConfig().server.ldSupport;
817
812
  let i;
818
813
 
@@ -832,7 +827,7 @@ function loadContextRoutesNGSILD(router) {
832
827
  router.patch('/ngsi-ld/v1/entities/:entity', [
833
828
  preprocessNGSILD,
834
829
  validateNGSILD(support.null, support.datasetId),
835
- handleMergePatchNgsiLD,
830
+ handleMergePatchNgsiLD,
836
831
  ErrorHandlingNgsiLD('Merge-Patch')
837
832
  ]);
838
833
 
@@ -503,7 +503,7 @@ function handleQueryNgsi2(req, res, next) {
503
503
  getFunction(function handleFindDevice(error, innerDevice) {
504
504
  let deviceList = [];
505
505
  if (!innerDevice) {
506
- return callback(new errors.DeviceNotFound(contextEntity.id));
506
+ return callback(new errors.DeviceNotFound(contextEntity.id), contextEntity);
507
507
  }
508
508
 
509
509
  if (innerDevice.count) {
@@ -263,40 +263,50 @@ function handleGetDevice(req, res, next) {
263
263
  } else if (device) {
264
264
  res.status(200).json(toProvisioningAPIFormat(device));
265
265
  } else {
266
- next(new errors.DeviceNotFound(req.params.deviceId));
266
+ next(new errors.DeviceNotFound(req.params.deviceId), {
267
+ apikey: req.query.apikey,
268
+ service: req.headers['fiware-service'],
269
+ subservice: req.headers['fiware-servicepath']
270
+ });
267
271
  }
268
272
  }
269
273
  );
270
274
  }
271
275
 
272
- /**
273
- * This middleware handles the removal of a particular device specified with the deviceId.
274
- */
275
- function handleRemoveDevice(req, res, next) {
276
- function getDevice(deviceId, apikey, service, subservice, callback) {
277
- deviceService.getDevice(deviceId, apikey, service, subservice, function (error, device) {
278
- if (error) {
279
- callback(error);
280
- } else if (device) {
281
- callback(null, device);
282
- } else {
283
- callback(new errors.DeviceNotFound(deviceId));
284
- }
285
- });
286
- }
287
-
288
- function applyRemoveDeviceHandler(device, callback) {
289
- if (removeDeviceHandler) {
290
- removeDeviceHandler(device, callback);
291
- } else {
276
+ function getDevice(deviceId, apikey, service, subservice, callback) {
277
+ deviceService.getDevice(deviceId, apikey, service, subservice, function (error, device) {
278
+ if (error) {
279
+ callback(error);
280
+ } else if (device) {
292
281
  callback(null, device);
282
+ } else {
283
+ callback(
284
+ new errors.DeviceNotFound(deviceId, {
285
+ apikey: apikey,
286
+ service: service,
287
+ subservice: subservice
288
+ })
289
+ );
293
290
  }
294
- }
291
+ });
292
+ }
295
293
 
296
- function unregisterDevice(deviceId, apikey, service, subservice, device, callback) {
297
- return deviceService.unregister(deviceId, apikey, service, subservice, callback);
294
+ function applyRemoveDeviceHandler(device, callback) {
295
+ if (removeDeviceHandler) {
296
+ removeDeviceHandler(device, callback);
297
+ } else {
298
+ callback(null, device);
298
299
  }
300
+ }
299
301
 
302
+ function unregisterDevice(deviceId, apikey, service, subservice, device, callback) {
303
+ return deviceService.unregister(deviceId, apikey, service, subservice, callback);
304
+ }
305
+
306
+ /**
307
+ * This middleware handles the removal of a particular device specified with the deviceId.
308
+ */
309
+ function handleRemoveDevice(req, res, next) {
300
310
  async.waterfall(
301
311
  [
302
312
  apply(statsRegistry.add, 'deviceRemovalRequests', 1),
@@ -320,7 +330,13 @@ function handleRemoveDevice(req, res, next) {
320
330
  if (error && error.code !== 404) {
321
331
  next(error);
322
332
  } else if (error && error.code === 404) {
323
- next(new errors.DeviceNotFound(req.params.deviceId));
333
+ next(
334
+ new errors.DeviceNotFound(req.params.deviceId, {
335
+ apikey: req.query.apikey,
336
+ service: req.headers['fiware-service'],
337
+ subservice: req.headers['fiware-servicepath']
338
+ })
339
+ );
324
340
  } else {
325
341
  res.status(204).send();
326
342
  }
@@ -328,6 +344,56 @@ function handleRemoveDevice(req, res, next) {
328
344
  );
329
345
  }
330
346
 
347
+ /**
348
+ * This middleware handles the removal of several devices specified in a array into a body
349
+ */
350
+ function handleRemoveDevices(req, res, next) {
351
+ logger.debug(context, 'Handling delete of devices: %j', req.body);
352
+ let theErrorOut = false;
353
+ for (let devicetoRemove of req.body.devices) {
354
+ let theError = theErrorOut;
355
+ async.waterfall(
356
+ [
357
+ apply(statsRegistry.add, 'deviceRemovalRequests', 1),
358
+ apply(
359
+ getDevice,
360
+ devicetoRemove.deviceId,
361
+ devicetoRemove.apikey,
362
+ req.headers['fiware-service'],
363
+ req.headers['fiware-servicepath']
364
+ ),
365
+ applyRemoveDeviceHandler,
366
+ apply(
367
+ unregisterDevice,
368
+ devicetoRemove.deviceId,
369
+ devicetoRemove.apikey,
370
+ req.headers['fiware-service'],
371
+ req.headers['fiware-servicepath']
372
+ )
373
+ ],
374
+ function (error) {
375
+ if (error && error.code !== 404) {
376
+ theError = !theError ? error : theError;
377
+ } else if (error && error.code === 404) {
378
+ theError = !theError
379
+ ? new errors.DeviceNotFound(devicetoRemove.deviceId, {
380
+ apikey: devicetoRemove.apikey,
381
+ service: req.headers['fiware-service'],
382
+ subservice: req.headers['fiware-servicepath']
383
+ })
384
+ : theError;
385
+ }
386
+ }
387
+ ); // waterfall
388
+ theErrorOut = theError;
389
+ } // for
390
+ if (theErrorOut) {
391
+ next(theErrorOut);
392
+ } else {
393
+ res.status(204).send();
394
+ }
395
+ }
396
+
331
397
  /**
332
398
  * This middleware handles updates in the provisioning devices. The only attribute
333
399
  */
@@ -379,7 +445,13 @@ function handleUpdateDevice(req, res, next) {
379
445
  }
380
446
  );
381
447
  } else {
382
- next(new errors.DeviceNotFound(req.params.deviceId));
448
+ next(
449
+ new errors.DeviceNotFound(req.params.deviceId, {
450
+ apikey: req.query.apikey,
451
+ service: req.headers['fiware-service'],
452
+ subservice: req.headers['fiware-servicepath']
453
+ })
454
+ );
383
455
  }
384
456
  }
385
457
  );
@@ -414,6 +486,8 @@ function loadContextRoutes(router) {
414
486
  handleUpdateDevice
415
487
  );
416
488
 
489
+ router.post('/iot/op/delete', restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleRemoveDevices);
490
+
417
491
  router.delete(
418
492
  '/iot/devices/:deviceId',
419
493
  restUtils.checkRequestAttributes('headers', mandatoryHeaders),
@@ -65,7 +65,7 @@ function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
65
65
  }
66
66
 
67
67
  if (missing.length !== 0) {
68
- const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing));
68
+ const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing), body);
69
69
  error.code = '400';
70
70
 
71
71
  callback(error);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "iotagent-node-lib",
3
3
  "license": "AGPL-3.0-only",
4
4
  "description": "IoT Agent library to interface with NGSI Context Broker",
5
- "version": "4.2.0",
5
+ "version": "4.4.0",
6
6
  "homepage": "https://github.com/telefonicaid/iotagent-node-lib",
7
7
  "keywords": [
8
8
  "fiware",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "async": "2.6.4",
47
47
  "body-parser": "~1.20.0",
48
- "express": "~4.18.1",
48
+ "express": "~4.19.2",
49
49
  "got": "~11.8.5",
50
50
  "jexl": "2.3.0",
51
51
  "jison": "0.4.18",
@@ -1,3 +1,3 @@
1
1
  matplotlib==3.7.1
2
2
  pandas==2.0.2
3
- pymongo==4.3.3
3
+ pymongo==4.6.3