iotagent-node-lib 4.4.0 → 4.6.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.
- package/README.md +67 -272
- package/config.js +2 -1
- package/doc/README.md +1 -1
- package/doc/admin.md +19 -22
- package/doc/api.md +621 -155
- package/doc/deprecated.md +4 -0
- package/doc/devel/architecture.md +5 -135
- package/doc/devel/development.md +224 -12
- package/doc/getting-started.md +114 -53
- package/doc/requirements.txt +1 -1
- package/doc/roadmap.md +5 -5
- package/docker/Mosquitto/Dockerfile +2 -2
- package/docker/Mosquitto/README.md +14 -11
- package/lib/commonConfig.js +7 -1
- package/lib/constants.js +3 -0
- package/lib/fiware-iotagent-lib.js +12 -15
- package/lib/jexlTranformsMap.js +3 -1
- package/lib/model/dbConn.js +1 -4
- package/lib/services/common/alarmManagement.js +3 -0
- package/lib/services/devices/deviceRegistryMemory.js +1 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +2 -2
- package/lib/services/devices/deviceService.js +4 -3
- package/lib/services/devices/devices-NGSI-LD.js +5 -5
- package/lib/services/devices/devices-NGSI-mixed.js +3 -3
- package/lib/services/devices/devices-NGSI-v2.js +5 -5
- package/lib/services/groups/groupService.js +1 -1
- package/lib/services/ngsi/entities-NGSI-LD.js +321 -571
- package/lib/services/ngsi/entities-NGSI-v2.js +348 -281
- package/lib/services/ngsi/ngsiService.js +3 -1
- package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
- package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
- package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
- package/lib/services/northBound/deviceProvisioningServer.js +5 -5
- package/lib/services/northBound/northboundServer.js +4 -2
- package/lib/services/stats/statsRegistry.js +128 -101
- package/lib/templates/createDevice.json +0 -24
- package/lib/templates/createDeviceLax.json +0 -23
- package/lib/templates/deviceGroup.json +1 -25
- package/lib/templates/updateDevice.json +0 -24
- package/lib/templates/updateDeviceLax.json +0 -23
- package/package.json +2 -2
- package/scripts/legacy_expression_tool/README.md +0 -1
- package/test/functional/README.md +75 -47
- package/test/functional/functional-tests-runner.js +9 -4
- package/test/functional/functional-tests.js +4 -4
- package/test/functional/testCases.js +1251 -257
- package/test/functional/testUtils.js +53 -20
- package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
- package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
- package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
- package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
- package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
- package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
- package/test/unit/general/deviceService-test.js +102 -0
- package/test/unit/general/statistics-service_test.js +1 -74
- package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +2 -3
- package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
- package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -102
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
- package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
- package/test/unit/ngsiv2/general/deviceService-test.js +94 -1
- package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
- package/test/unit/ngsiv2/provisioning/device-group-api-test.js +259 -0
- package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1189 -0
- package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
- package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +0 -4
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
- package/test/unit/statsRegistry/openmetrics-test.js +167 -0
- package/lib/templates/queryContext.json +0 -25
- package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
- package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
- package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
- package/test/unit/general/statistics-persistence_test.js +0 -121
- package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
- package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
- package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
const async = require('async');
|
|
28
28
|
const apply = async.apply;
|
|
29
|
+
const statsRegistry = require('../stats/statsRegistry');
|
|
29
30
|
const intoTrans = require('../common/domain').intoTrans;
|
|
30
31
|
const fillService = require('./../common/domain').fillService;
|
|
31
32
|
const errors = require('../../errors');
|
|
@@ -67,7 +68,8 @@ function init() {
|
|
|
67
68
|
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
68
69
|
*/
|
|
69
70
|
function sendUpdateValue(entityName, attributes, typeInformation, token, callback) {
|
|
70
|
-
|
|
71
|
+
const newCallback = statsRegistry.withStats('updateEntityRequestsOk', 'updateEntityRequestsError', callback);
|
|
72
|
+
entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, newCallback);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -89,7 +89,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
|
|
|
89
89
|
triggers
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
config.getRegistry().update(device, callback);
|
|
92
|
+
config.getRegistry().update(device, device, callback);
|
|
93
93
|
} else {
|
|
94
94
|
callback(null, response.headers.location);
|
|
95
95
|
}
|
|
@@ -203,7 +203,7 @@ function createUnsubscribeHandlerNgsiLD(device, id, callback) {
|
|
|
203
203
|
callback(new errors.BadRequest(body.orionError.details));
|
|
204
204
|
} else {
|
|
205
205
|
device.subscriptions.splice(device.subscriptions.indexOf(id), 1);
|
|
206
|
-
config.getRegistry().update(device, callback);
|
|
206
|
+
config.getRegistry().update(device, device, callback);
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
}
|
|
@@ -91,7 +91,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
|
|
|
91
91
|
triggers
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
config.getRegistry().update(device, callback);
|
|
94
|
+
config.getRegistry().update(device, device, callback);
|
|
95
95
|
} else {
|
|
96
96
|
callback(null, response.headers.location);
|
|
97
97
|
}
|
|
@@ -203,7 +203,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
|
|
|
203
203
|
callback(new errors.BadRequest(body.orionError.details));
|
|
204
204
|
} else {
|
|
205
205
|
device.subscriptions.splice(device.subscriptions.indexOf(id), 1);
|
|
206
|
-
config.getRegistry().update(device, callback);
|
|
206
|
+
config.getRegistry().update(device, device, callback);
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
}
|
|
@@ -29,13 +29,19 @@ const restUtils = require('./restUtils');
|
|
|
29
29
|
const groupService = require('./../groups/groupService');
|
|
30
30
|
const async = require('async');
|
|
31
31
|
const apply = async.apply;
|
|
32
|
-
const
|
|
32
|
+
const templateGroupService = require('../../templates/deviceGroup.json');
|
|
33
33
|
let configurationHandler;
|
|
34
34
|
let removeConfigurationHandler;
|
|
35
35
|
let configurationMiddleware = [];
|
|
36
36
|
const _ = require('underscore');
|
|
37
37
|
const mandatoryHeaders = ['fiware-service', 'fiware-servicepath'];
|
|
38
38
|
const mandatoryParameters = ['resource', 'apikey'];
|
|
39
|
+
const constants = require('../../constants');
|
|
40
|
+
|
|
41
|
+
// Create new template for configuration groups replacing services by CONFIGGROUP_TERM
|
|
42
|
+
const templateGroup = JSON.parse(JSON.stringify(templateGroupService));
|
|
43
|
+
templateGroup.properties[constants.CONFIGGROUP_TERM] = templateGroup.properties.services;
|
|
44
|
+
delete templateGroup.properties.services;
|
|
39
45
|
|
|
40
46
|
const apiToInternal = {
|
|
41
47
|
entity_type: 'type',
|
|
@@ -118,6 +124,8 @@ function applyConfigurationMiddlewares(newConfiguration, callback) {
|
|
|
118
124
|
* @param {Function} next Invokes the next middleware in the chain.
|
|
119
125
|
*/
|
|
120
126
|
function handleCreateDeviceGroup(req, res, next) {
|
|
127
|
+
req.body = checkAndModifyGroupRecv(req._parsedUrl.pathname, req.body); // #FIXME1649: This line should be removed when /iot/services support is removed
|
|
128
|
+
|
|
121
129
|
for (let i = 0; i < req.body.services.length; i++) {
|
|
122
130
|
req.body.services[i] = applyMap(apiToInternal, req.body.services[i]);
|
|
123
131
|
req.body.services[i].service = req.headers['fiware-service'];
|
|
@@ -161,8 +169,7 @@ function handleListDeviceGroups(req, res, next) {
|
|
|
161
169
|
} else {
|
|
162
170
|
translatedGroup = applyMap(internalToApi, group);
|
|
163
171
|
}
|
|
164
|
-
|
|
165
|
-
res.status(200).send(translatedGroup);
|
|
172
|
+
res.status(200).send(checkAndModifyGroupResp(req._parsedUrl.pathname, translatedGroup)); // #FIXME1649: Response should not use checkAndModifyGroupResp function when /iot/services support is removed
|
|
166
173
|
}
|
|
167
174
|
};
|
|
168
175
|
|
|
@@ -263,25 +270,37 @@ function handleDeleteDeviceGroups(req, res, next) {
|
|
|
263
270
|
*
|
|
264
271
|
* @param {Object} router Express request router object.
|
|
265
272
|
*/
|
|
273
|
+
// #FIXME1649: Routes using /iot/services path should be removed when support for it is removed
|
|
266
274
|
function loadContextRoutes(router) {
|
|
267
275
|
router.post(
|
|
268
276
|
'/iot/services',
|
|
269
277
|
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
|
|
278
|
+
restUtils.checkBody(templateGroupService),
|
|
279
|
+
handleCreateDeviceGroup
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
router.post(
|
|
283
|
+
'/iot/groups',
|
|
284
|
+
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
|
|
270
285
|
restUtils.checkBody(templateGroup),
|
|
271
286
|
handleCreateDeviceGroup
|
|
272
287
|
);
|
|
273
288
|
|
|
274
|
-
router.get(
|
|
289
|
+
router.get(
|
|
290
|
+
['/iot/services', '/iot/groups'],
|
|
291
|
+
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
|
|
292
|
+
handleListDeviceGroups
|
|
293
|
+
);
|
|
275
294
|
|
|
276
295
|
router.put(
|
|
277
|
-
'/iot/services',
|
|
296
|
+
['/iot/services', '/iot/groups'],
|
|
278
297
|
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
|
|
279
298
|
restUtils.checkRequestAttributes('query', mandatoryParameters),
|
|
280
299
|
handleModifyDeviceGroups
|
|
281
300
|
);
|
|
282
301
|
|
|
283
302
|
router.delete(
|
|
284
|
-
'/iot/services',
|
|
303
|
+
['/iot/services', '/iot/groups'],
|
|
285
304
|
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
|
|
286
305
|
restUtils.checkRequestAttributes('query', mandatoryParameters),
|
|
287
306
|
handleDeleteDeviceGroups
|
|
@@ -306,6 +325,23 @@ function clear(callback) {
|
|
|
306
325
|
callback();
|
|
307
326
|
}
|
|
308
327
|
|
|
328
|
+
// #FIXME1649: This function should be removed when /iot/services support is removed
|
|
329
|
+
function checkAndModifyGroupResp(route, payload) {
|
|
330
|
+
if (route === '/iot/groups') {
|
|
331
|
+
payload['groups'] = payload['services'];
|
|
332
|
+
delete payload['services'];
|
|
333
|
+
}
|
|
334
|
+
return payload;
|
|
335
|
+
}
|
|
336
|
+
// #FIXME1649: This function should be removed when /iot/services support is removed
|
|
337
|
+
function checkAndModifyGroupRecv(route, payload) {
|
|
338
|
+
if (route === '/iot/groups') {
|
|
339
|
+
payload['services'] = payload['groups'];
|
|
340
|
+
delete payload['groups'];
|
|
341
|
+
}
|
|
342
|
+
return payload;
|
|
343
|
+
}
|
|
344
|
+
|
|
309
345
|
exports.loadContextRoutes = loadContextRoutes;
|
|
310
346
|
exports.setConfigurationHandler = setConfigurationHandler;
|
|
311
347
|
exports.setRemoveConfigurationHandler = setRemoveConfigurationHandler;
|
|
@@ -182,7 +182,6 @@ function attributeToProvisioningAPIFormat(attribute) {
|
|
|
182
182
|
type: attribute.type,
|
|
183
183
|
expression: attribute.expression,
|
|
184
184
|
skipValue: attribute.skipValue,
|
|
185
|
-
reverse: attribute.reverse,
|
|
186
185
|
entity_name: attribute.entity_name,
|
|
187
186
|
entity_type: attribute.entity_type,
|
|
188
187
|
mqtt: attribute.mqtt,
|
|
@@ -398,11 +397,11 @@ function handleRemoveDevices(req, res, next) {
|
|
|
398
397
|
* This middleware handles updates in the provisioning devices. The only attribute
|
|
399
398
|
*/
|
|
400
399
|
function handleUpdateDevice(req, res, next) {
|
|
401
|
-
function applyUpdatingHandler(
|
|
400
|
+
function applyUpdatingHandler(newDevice, oldDevice, callback) {
|
|
402
401
|
if (updatingHandler) {
|
|
403
|
-
updatingHandler(
|
|
402
|
+
updatingHandler(newDevice, oldDevice, callback);
|
|
404
403
|
} else {
|
|
405
|
-
callback(null,
|
|
404
|
+
callback(null, newDevice);
|
|
406
405
|
}
|
|
407
406
|
}
|
|
408
407
|
|
|
@@ -429,10 +428,11 @@ function handleUpdateDevice(req, res, next) {
|
|
|
429
428
|
isTypeOrNameUpdated = true;
|
|
430
429
|
}
|
|
431
430
|
async.waterfall(
|
|
432
|
-
[apply(applyUpdatingHandler, newDevice)],
|
|
431
|
+
[apply(applyUpdatingHandler, newDevice, device)],
|
|
433
432
|
function handleUpdating(error, newDeviceUpdated) {
|
|
434
433
|
deviceService.updateRegister(
|
|
435
434
|
newDeviceUpdated,
|
|
435
|
+
device,
|
|
436
436
|
isTypeOrNameUpdated,
|
|
437
437
|
function handleDeviceUpdate(error) {
|
|
438
438
|
if (error) {
|
|
@@ -33,6 +33,7 @@ const intoTrans = domainUtils.intoTrans;
|
|
|
33
33
|
const deviceProvisioning = require('./deviceProvisioningServer');
|
|
34
34
|
const deviceUpdating = require('./deviceProvisioningServer');
|
|
35
35
|
const groupProvisioning = require('./deviceGroupAdministrationServer');
|
|
36
|
+
const statsRegistry = require('../stats/statsRegistry');
|
|
36
37
|
const logger = require('logops');
|
|
37
38
|
const context = {
|
|
38
39
|
op: 'IoTAgentNGSI.NorthboundServer'
|
|
@@ -56,8 +57,8 @@ function start(config, callback) {
|
|
|
56
57
|
northboundServer.app.set('port', config.server.port);
|
|
57
58
|
northboundServer.app.set('host', config.server.host || '0.0.0.0');
|
|
58
59
|
northboundServer.app.use(domainUtils.requestDomain);
|
|
59
|
-
northboundServer.app.use(bodyParser.json());
|
|
60
|
-
northboundServer.app.use(bodyParser.json({ type: 'application/*+json' }));
|
|
60
|
+
northboundServer.app.use(bodyParser.json({ limit: config.expressLimit }));
|
|
61
|
+
northboundServer.app.use(bodyParser.json({ type: 'application/*+json', limit: config.expressLimit }));
|
|
61
62
|
|
|
62
63
|
if (config.logLevel && config.logLevel === 'DEBUG') {
|
|
63
64
|
northboundServer.app.use(middlewares.traceRequest);
|
|
@@ -83,6 +84,7 @@ function start(config, callback) {
|
|
|
83
84
|
northboundServer.router.get('/version', middlewares.retrieveVersion);
|
|
84
85
|
northboundServer.router.put('/admin/log', middlewares.changeLogLevel);
|
|
85
86
|
northboundServer.router.get('/admin/log', middlewares.getLogLevel);
|
|
87
|
+
northboundServer.router.get('/metrics', statsRegistry.openmetricsHandler);
|
|
86
88
|
|
|
87
89
|
northboundServer.app.use(baseRoot, northboundServer.router);
|
|
88
90
|
contextServer.loadContextRoutes(northboundServer.router);
|
|
@@ -23,16 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
/* eslint-disable no-prototype-builtins */
|
|
25
25
|
|
|
26
|
-
const async = require('async');
|
|
27
26
|
const _ = require('underscore');
|
|
28
|
-
const apply = async.apply;
|
|
29
27
|
const logger = require('logops');
|
|
30
|
-
const config = require('../../commonConfig');
|
|
31
|
-
const dbService = require('../../model/dbConn');
|
|
32
28
|
let globalStats = {};
|
|
33
|
-
let currentStats = {};
|
|
34
|
-
let timerActions = [];
|
|
35
|
-
let timerHandler;
|
|
36
29
|
const statsContext = {
|
|
37
30
|
op: 'IoTAgentNGSI.TimedStats'
|
|
38
31
|
};
|
|
@@ -45,30 +38,14 @@ const statsContext = {
|
|
|
45
38
|
* @param {Number} value Value to be added to the total.
|
|
46
39
|
*/
|
|
47
40
|
function add(key, value, callback) {
|
|
48
|
-
if (currentStats[key]) {
|
|
49
|
-
currentStats[key] += value;
|
|
50
|
-
} else {
|
|
51
|
-
currentStats[key] = value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
41
|
if (globalStats[key]) {
|
|
55
42
|
globalStats[key] += value;
|
|
56
43
|
} else {
|
|
57
44
|
globalStats[key] = value;
|
|
58
45
|
}
|
|
59
|
-
|
|
60
46
|
callback(null);
|
|
61
47
|
}
|
|
62
48
|
|
|
63
|
-
/**
|
|
64
|
-
* Get the current value of a particular stat.
|
|
65
|
-
*
|
|
66
|
-
* @param {String} key Name of the stat to retrive.
|
|
67
|
-
*/
|
|
68
|
-
function getCurrent(key, callback) {
|
|
69
|
-
callback(null, currentStats[key]);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
49
|
/**
|
|
73
50
|
* Get the global value of the selected attribute.
|
|
74
51
|
*
|
|
@@ -85,112 +62,162 @@ function getAllGlobal(callback) {
|
|
|
85
62
|
callback(null, globalStats);
|
|
86
63
|
}
|
|
87
64
|
|
|
88
|
-
/**
|
|
89
|
-
* Get all the current stats currently stored in the repository.
|
|
90
|
-
*/
|
|
91
|
-
function getAllCurrent(callback) {
|
|
92
|
-
callback(null, currentStats);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
65
|
/**
|
|
96
66
|
* Loads the values passed as parameters into the global statistics repository.
|
|
97
67
|
*
|
|
98
68
|
* @param {Object} values Key-value map with the values to be load.
|
|
99
69
|
*/
|
|
100
70
|
function globalLoad(values, callback) {
|
|
101
|
-
globalStats = values;
|
|
102
|
-
currentStats = {};
|
|
103
|
-
|
|
104
|
-
for (const i in values) {
|
|
105
|
-
if (values.hasOwnProperty(i)) {
|
|
106
|
-
currentStats[i] = 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
71
|
+
globalStats = _.clone(values);
|
|
110
72
|
callback(null);
|
|
111
73
|
}
|
|
112
74
|
|
|
113
75
|
/**
|
|
114
|
-
*
|
|
76
|
+
* Chooses the appropiate content type and version based on Accept header
|
|
77
|
+
*
|
|
78
|
+
* @param {String} accepts The accepts header
|
|
115
79
|
*/
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
80
|
+
function matchContentType(accepts) {
|
|
81
|
+
const requestedType = [];
|
|
82
|
+
const vlabel = 'version=';
|
|
83
|
+
const clabel = 'charset=';
|
|
84
|
+
const qlabel = 'q=';
|
|
85
|
+
for (const expression of accepts.split(',')) {
|
|
86
|
+
const parts = expression.split(';').map((part) => part.trim());
|
|
87
|
+
const mediaType = parts[0];
|
|
88
|
+
let version = null;
|
|
89
|
+
let charset = null;
|
|
90
|
+
let preference = null;
|
|
91
|
+
for (let part of parts.slice(1)) {
|
|
92
|
+
if (part.startsWith(vlabel)) {
|
|
93
|
+
version = part.substring(vlabel.length).trim();
|
|
94
|
+
} else if (part.startsWith(clabel)) {
|
|
95
|
+
charset = part.substring(clabel.length).trim();
|
|
96
|
+
} else if (part.startsWith(qlabel)) {
|
|
97
|
+
preference = parseFloat(part.substring(qlabel.length).trim());
|
|
98
|
+
}
|
|
120
99
|
}
|
|
100
|
+
requestedType.push({
|
|
101
|
+
mediaType: mediaType,
|
|
102
|
+
version: version,
|
|
103
|
+
charset: charset,
|
|
104
|
+
preference: preference || 1.0
|
|
105
|
+
});
|
|
121
106
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
* Executes all the stored timer actions when a timer click is received.
|
|
128
|
-
*/
|
|
129
|
-
function tickHandler() {
|
|
130
|
-
process.nextTick(apply(async.series, timerActions));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Adds a new timer action to the timerActions Array, activating the timer if it was not previously activated.
|
|
135
|
-
*
|
|
136
|
-
* @param {Function} handler Action to be executed. Should take two statistics objects and a callback.
|
|
137
|
-
*/
|
|
138
|
-
function addTimerAction(handler, callback) {
|
|
139
|
-
if (!timerHandler && config.getConfig().stats.interval) {
|
|
140
|
-
timerHandler = setInterval(tickHandler, config.getConfig().stats.interval);
|
|
107
|
+
// If both text/plain and openmetrics are accepted,
|
|
108
|
+
// prefer openmetrics
|
|
109
|
+
const mediaTypePref = {
|
|
110
|
+
'application/openmetrics-text': 1.0,
|
|
111
|
+
'text/plain': 0.5
|
|
141
112
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
113
|
+
// sort requests by priority descending
|
|
114
|
+
requestedType.sort(function (a, b) {
|
|
115
|
+
if (a.preference === b.preference) {
|
|
116
|
+
// same priority, sort by media type.
|
|
117
|
+
return (mediaTypePref[b.mediaType] || 0) - (mediaTypePref[a.mediaType] || 0);
|
|
118
|
+
}
|
|
119
|
+
return b.preference - a.preference;
|
|
120
|
+
});
|
|
121
|
+
for (const req of requestedType) {
|
|
122
|
+
switch(req.mediaType) {
|
|
123
|
+
case 'application/openmetrics-text':
|
|
124
|
+
req.version = req.version || '1.0.0';
|
|
125
|
+
req.charset = req.charset || 'utf-8';
|
|
126
|
+
if (
|
|
127
|
+
(req.version === '1.0.0' || req.version === '0.0.1') &&
|
|
128
|
+
(req.charset === 'utf-8')) {
|
|
129
|
+
return req;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case 'text/plain':
|
|
133
|
+
case 'text/*':
|
|
134
|
+
case '*/*':
|
|
135
|
+
req.version = req.version || '0.0.4';
|
|
136
|
+
req.charset = req.charset || 'utf-8';
|
|
137
|
+
if (
|
|
138
|
+
(req.version === '0.0.4') &&
|
|
139
|
+
(req.charset === 'utf-8')) {
|
|
140
|
+
req.mediaType = 'text/plain';
|
|
141
|
+
return req;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
/**
|
|
148
|
-
*
|
|
150
|
+
* Predefined http handler that returns current openmetrics data
|
|
149
151
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
153
|
+
function openmetricsHandler(req, res) {
|
|
154
|
+
// Content-Type:
|
|
155
|
+
// - For openmetrics collectors, it MUST BE 'application/openmetrics-text; version=1.0.0; charset=utf-8'. See:
|
|
156
|
+
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#overall-structure
|
|
157
|
+
// - For prometheus compatible collectors, it SHOULD BE 'text/plain; version=0.0.4; charset=utf-8'. See:
|
|
158
|
+
// https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
|
|
159
|
+
// - Caveat: Some versions of prometheus have been observed to send multivalued Accept headers such as
|
|
160
|
+
// Accept: application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1
|
|
161
|
+
let reqType = {
|
|
162
|
+
mediaType: 'application/openmetrics-text',
|
|
163
|
+
version: '1.0.0',
|
|
164
|
+
charset: 'utf-8'
|
|
154
165
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
if (req.headers.accept) {
|
|
167
|
+
// WORKAROUND: express version 4 does not parse properly the openmetrics Accept header,
|
|
168
|
+
// it won't match the regular expressions supported by `express.accepts`.
|
|
169
|
+
// So we must parse these key-value pairs ourselves.
|
|
170
|
+
reqType = matchContentType(req.headers.accept);
|
|
171
|
+
if (reqType === null) {
|
|
172
|
+
logger.error(statsContext, 'Unsupported media type: %s', req.headers.accept);
|
|
173
|
+
res.status(406).send('Not Acceptable');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const contentType = `${reqType.mediaType};version=${reqType.version};charset=${reqType.charset}`;
|
|
178
|
+
// The actual payload is the same for all supported content types
|
|
179
|
+
const metrics = [];
|
|
180
|
+
for (const key in globalStats) {
|
|
181
|
+
if (globalStats.hasOwnProperty(key)) {
|
|
182
|
+
metrics.push('# HELP ' + key + ' global metric for ' + key);
|
|
183
|
+
metrics.push('# TYPE ' + key + ' counter');
|
|
184
|
+
metrics.push(key + ' ' + globalStats[key]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Expositions MUST END WITH '#EOF'
|
|
188
|
+
// See https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md
|
|
189
|
+
metrics.push('# EOF');
|
|
190
|
+
res.set('Content-Type', contentType);
|
|
191
|
+
res.status(200).send(metrics.join('\n'));
|
|
158
192
|
}
|
|
159
193
|
|
|
160
194
|
/**
|
|
161
|
-
*
|
|
195
|
+
* Wraps a callback with stats, incrementing the given counters
|
|
196
|
+
* depending on the parameters passed to the callback:
|
|
162
197
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*/
|
|
166
|
-
function logStats(currentValues, globalValues, callback) {
|
|
167
|
-
logger.info(statsContext, 'Global stat values:\n%s\n', JSON.stringify(globalValues, null, 4));
|
|
168
|
-
logger.info(statsContext, 'Current stat values:\n%s\n', JSON.stringify(currentValues, null, 4));
|
|
169
|
-
|
|
170
|
-
resetCurrent(callback);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Predefined action that persists the current value of the stats in the MongoDb instance.
|
|
198
|
+
* - If the callback receives an error, the errCounter is incremented.
|
|
199
|
+
* - If the callback receives no error, the okCounter is incremented.
|
|
175
200
|
*
|
|
176
|
-
* @param {
|
|
177
|
-
* @param {
|
|
201
|
+
* @param {String} okCounter Name of the counter to increment on success.
|
|
202
|
+
* @param {String} errCounter Name of the counter to increment on error.
|
|
203
|
+
* @param {Function} callback Callback to wrap. It must be a function that can
|
|
204
|
+
* expect any number of parameters, but the first one must
|
|
205
|
+
* be an indication of the error occured, if any.
|
|
178
206
|
*/
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
function withStats(okCounter, errCounter, callback) {
|
|
208
|
+
function accounting(...args) {
|
|
209
|
+
const counter = args.length > 0 && args[0] ? errCounter : okCounter;
|
|
210
|
+
add(counter, 1, function () {
|
|
211
|
+
callback(...args);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return accounting;
|
|
184
215
|
}
|
|
185
216
|
|
|
186
217
|
exports.add = add;
|
|
187
|
-
exports.getCurrent = getCurrent;
|
|
188
218
|
exports.getGlobal = getGlobal;
|
|
189
219
|
exports.getAllGlobal = getAllGlobal;
|
|
190
|
-
exports.getAllCurrent = getAllCurrent;
|
|
191
220
|
exports.globalLoad = globalLoad;
|
|
192
|
-
exports.
|
|
193
|
-
exports.
|
|
194
|
-
exports.
|
|
195
|
-
exports.logStats = logStats;
|
|
196
|
-
exports.mongodbPersistence = mongodbPersistence;
|
|
221
|
+
exports.withStats = withStats;
|
|
222
|
+
exports.openmetricsHandler = openmetricsHandler;
|
|
223
|
+
exports.matchContentType = matchContentType;
|
|
@@ -119,30 +119,6 @@
|
|
|
119
119
|
"type": "string",
|
|
120
120
|
"pattern": "^([^<>;'=\"]+)+$"
|
|
121
121
|
},
|
|
122
|
-
"reverse": {
|
|
123
|
-
"description": "Define the attribute as bidirectional",
|
|
124
|
-
"type": "array",
|
|
125
|
-
"items": {
|
|
126
|
-
"type": "object",
|
|
127
|
-
"additionalProperties": false,
|
|
128
|
-
"properties": {
|
|
129
|
-
"object_id": {
|
|
130
|
-
"description": "ID of the attribute in the device",
|
|
131
|
-
"type": "string",
|
|
132
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
133
|
-
},
|
|
134
|
-
"type": {
|
|
135
|
-
"description": "Type of the attribute in the target entity",
|
|
136
|
-
"type": "string",
|
|
137
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
138
|
-
},
|
|
139
|
-
"expression": {
|
|
140
|
-
"description": "Optional expression for measurement transformation",
|
|
141
|
-
"type": "string"
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
122
|
"metadata": {
|
|
147
123
|
"description": "Attribute Metadata",
|
|
148
124
|
"type": "object"
|
|
@@ -116,29 +116,6 @@
|
|
|
116
116
|
"description": "Optional entity type for multientity updatess",
|
|
117
117
|
"type": "string",
|
|
118
118
|
"pattern": "^([^<>;'=\"]+)+$"
|
|
119
|
-
},
|
|
120
|
-
"reverse": {
|
|
121
|
-
"description": "Define the attribute as bidirectional",
|
|
122
|
-
"type": "array",
|
|
123
|
-
"items": {
|
|
124
|
-
"type": "object",
|
|
125
|
-
"additionalProperties": false,
|
|
126
|
-
"properties": {
|
|
127
|
-
"object_id": {
|
|
128
|
-
"description": "ID of the attribute in the device",
|
|
129
|
-
"type": "string"
|
|
130
|
-
},
|
|
131
|
-
"type": {
|
|
132
|
-
"description": "Type of the attribute in the target entity",
|
|
133
|
-
"type": "string",
|
|
134
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
135
|
-
},
|
|
136
|
-
"expression": {
|
|
137
|
-
"description": "Optional expression for measurement transformation",
|
|
138
|
-
"type": "string"
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
119
|
}
|
|
143
120
|
}
|
|
144
121
|
}
|
|
@@ -60,33 +60,9 @@
|
|
|
60
60
|
"commands": {
|
|
61
61
|
"description": "list of commands of the devices",
|
|
62
62
|
"type": "array"
|
|
63
|
-
},
|
|
64
|
-
"reverse": {
|
|
65
|
-
"description": "Define the attribute as bidirectional",
|
|
66
|
-
"type": "array",
|
|
67
|
-
"items": {
|
|
68
|
-
"type": "object",
|
|
69
|
-
"additionalProperties": false,
|
|
70
|
-
"properties": {
|
|
71
|
-
"object_id": {
|
|
72
|
-
"description": "ID of the attribute in the device",
|
|
73
|
-
"type": "string",
|
|
74
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
75
|
-
},
|
|
76
|
-
"type": {
|
|
77
|
-
"description": "Type of the attribute in the target entity",
|
|
78
|
-
"type": "string",
|
|
79
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
80
|
-
},
|
|
81
|
-
"expression": {
|
|
82
|
-
"description": "Optional expression for measurement transformation",
|
|
83
|
-
"type": "string"
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
63
|
}
|
|
88
64
|
}
|
|
89
65
|
}
|
|
90
66
|
}
|
|
91
|
-
}
|
|
67
|
+
}
|
|
92
68
|
}
|
|
@@ -105,30 +105,6 @@
|
|
|
105
105
|
"type": "string",
|
|
106
106
|
"pattern": "^([^<>;'=\"]+)+$"
|
|
107
107
|
},
|
|
108
|
-
"reverse": {
|
|
109
|
-
"description": "Define the attribute as bidirectional",
|
|
110
|
-
"type": "array",
|
|
111
|
-
"items": {
|
|
112
|
-
"type": "object",
|
|
113
|
-
"additionalProperties": false,
|
|
114
|
-
"properties": {
|
|
115
|
-
"object_id": {
|
|
116
|
-
"description": "ID of the attribute in the device",
|
|
117
|
-
"type": "string",
|
|
118
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
119
|
-
},
|
|
120
|
-
"type": {
|
|
121
|
-
"description": "Type of the attribute in the target entity",
|
|
122
|
-
"type": "string",
|
|
123
|
-
"pattern": "^([^<>();'=\"]+)+$"
|
|
124
|
-
},
|
|
125
|
-
"expression": {
|
|
126
|
-
"description": "Optional expression for measurement transformation",
|
|
127
|
-
"type": "string"
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
108
|
"metadata": {
|
|
133
109
|
"description": "Attribute Metadata",
|
|
134
110
|
"type": "object"
|