-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allow access entities using NGSIv2 API for non_signal rules #726
Changes from 16 commits
46d4bf3
cea4ca5
693a388
3f87d0e
08ca5db
ae7708d
2071248
b9a40f2
df171c0
0ac60c9
c046597
82a4910
801fa80
b7168c1
dcf84dd
f71575e
b0958b9
cabcd0d
4fd3bf0
b3ef4fe
3cdf6ce
c88639f
d2038d2
18c2c89
bb3f154
a289a3d
c036794
184f6b7
164fb54
b0ba12e
cb70ed8
87c1766
04543fa
b251606
7f16995
aa79f86
4b63d3d
a0b86c3
e593c4c
543cfdd
cb84048
ebdd267
145e798
92aa5fe
c513522
3020ed0
2f53ae4
38a2932
53f1a47
e240874
897e205
2369f6b
f3207ec
00dcd96
b2e11dd
855cde0
53796c1
3f3b8ef
06d20d8
8613403
7c6bcc8
d939e62
6cd8488
4086d69
b1808b2
249a179
023724b
5e55352
b1ac552
43501ba
790b9a6
77dd194
ba8cd92
7d86c11
860d818
9c6338f
ffa6fb8
c08ca19
f68a9df
927f42d
241a7a7
eac5024
aa3a6e4
05c0ef5
480d7cd
3eac6d4
fe27602
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file needs to be removed from PR. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,15 +28,89 @@ var async = require('async'), | |
entitiesCollectionName = require('../../config').orionDb.collection, | ||
myutils = require('../myutils'), | ||
constants = require('../constants'), | ||
logger = require('logops'); | ||
logger = require('logops'), | ||
NGSI = require('ngsijs'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's a reference identifier. It should be lowercase. To be NGSI it's not enougth :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solved in this commit: |
||
|
||
function orionServiceDb(service) { | ||
return appContext.OrionDb(config.orionDb.prefix + '-' + service); | ||
} | ||
|
||
function findSilentEntities(service, subservice, ruleData, func, callback) { | ||
function findSilentEntitiesByAPI(service, subservice, ruleData, func, callback) { | ||
var context = { op: 'checkNoSignal.findSilentEntitiesByAPI', comp: constants.COMPONENT_NAME }; | ||
|
||
var options = { | ||
service: service, | ||
servicepath: subservice | ||
}; | ||
options.headers = {}; | ||
// if (token !== null) { | ||
// options.headers[constants.AUTH_HEADER] = token; | ||
// } | ||
RafaelM3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Add correlator | ||
var domain = process.domain; | ||
if (domain && domain.context) { | ||
options.headers[constants.CORRELATOR_HEADER] = domain.context.corr; | ||
// Add other headers | ||
if (domain.context.srv && options.headers[constants.SERVICE_HEADER] === undefined) { | ||
options.headers[constants.SERVICE_HEADER] = domain.context.srv; | ||
} | ||
if (domain.context.subsrv && options.headers[constants.SUBSERVICE_HEADER] === undefined) { | ||
options.headers[constants.SUBSERVICE_HEADER] = domain.context.subsrv; | ||
} | ||
if (domain.context.from && options.headers[constants.REALIP_HEADER] === undefined) { | ||
options.headers[constants.REALIP_HEADER] = domain.context.from; | ||
} | ||
} | ||
var connection = new NGSI.Connection(config.orion.URL, options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not necessary to catch any error? are they managed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The callback function is in charge of error handling |
||
|
||
var filter = { | ||
service: service, | ||
servicepath: subservice, | ||
type: ruleData.type, | ||
mq: ruleData.attribute + '.dateModified<' + (Date.now() / 1000 - ruleData.reportInterval).toString() | ||
}; | ||
if (ruleData.id) { | ||
filter.id = ruleData.id; | ||
} else if (ruleData.idRegexp) { | ||
filter.idPattern = ruleData.idRegexp; | ||
} | ||
logger.debug( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would preffer INFO level There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solved in this commit: 184f6b7 |
||
context, | ||
'find silent entities by API ngsi using options %j and filter %j and rule %j', | ||
options, | ||
filter, | ||
ruleData | ||
); | ||
// https://ficodes.github.io/ngsijs/stable/NGSI.Connection.html#.%22v2.listEntities%22__anchor | ||
connection.v2.listEntities(filter).then( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the library takes pagination into account? it's there a problem with thousand of entities? I think it does, but check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solved in these commits: aa79f86#diff-d21abada9564b2a8fdaf4c061d51c73f386a5b4869659ab7517d94c9f665818c , cb70ed8 |
||
(response) => { | ||
// Entities retrieved successfully | ||
// response.correlator transaction id associated with the server response | ||
// response.limit contains the used page size | ||
// response.results is an array with the retrieved entities | ||
// response.offset contains the offset used in the request | ||
var count = 0; | ||
response.results.forEach((entity) => { | ||
logger.debug(context, 'silent entity %j', entity); | ||
func(entity); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. func has no meaning. Use some meaningful identifier for "func" param There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solved in this commit: a0b86c3 |
||
count++; | ||
}); | ||
logger.debug(context, 'findSilentEntities %s', myutils.firstChars(response.results)); | ||
callback(null, response.results); | ||
}, | ||
(error) => { | ||
// Error retrieving entities | ||
// If the error was reported by Orion, error.correlator will be | ||
// filled with the associated transaction id | ||
logger.warn('error v2.listEntities: %j trying list entities using filter %j', error, filter); | ||
callback(error, null); | ||
} | ||
); | ||
} | ||
|
||
function findSilentEntitiesByMongo(service, subservice, ruleData, func, callback) { | ||
var db, | ||
context = { op: 'checkNoSignal', comp: constants.COMPONENT_NAME }, | ||
context = { op: 'checkNoSignal.findSilentEntitiesByMongo', comp: constants.COMPONENT_NAME }, | ||
criterion = {}; | ||
|
||
db = orionServiceDb(service); | ||
|
@@ -88,6 +162,14 @@ function findSilentEntities(service, subservice, ruleData, func, callback) { | |
); | ||
} | ||
|
||
module.exports = { | ||
FindSilentEntities: findSilentEntities | ||
}; | ||
function findSilentEntities(service, subservice, ruleData, func, callback) { | ||
if (!config.nonSignalByAPI) { | ||
return findSilentEntitiesByMongo(service, subservice, ruleData, func, callback); | ||
} else { | ||
return findSilentEntitiesByAPI(service, subservice, ruleData, func, callback); | ||
} | ||
} | ||
|
||
module.exports.FindSilentEntities = findSilentEntities; | ||
module.exports.findSilentEntitiesByAPI = findSilentEntitiesByAPI; | ||
module.exports.findSilentEntitiesByMongo = findSilentEntitiesByMongo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,36 +63,63 @@ function alertFunc(nsLineRule, entity) { | |
d.exit(); | ||
}); | ||
d.run(function() { | ||
logger.debug(context, 'alertfunc nsLineRule %j entity %j ', nsLineRule, entity); | ||
// We duplicate info in event and event.ev for VR and non-VR action parameters | ||
var event = { | ||
service: nsLineRule[SERVICE], | ||
subservice: nsLineRule[SUBSERVICE], | ||
ruleName: nsLineRule[NAME], | ||
reportInterval: nsLineRule[REPORT_INTERVAL], | ||
id: entity._id.id, | ||
type: entity._id.type, | ||
internalCurrentTime: new Date().toISOString() | ||
}; | ||
|
||
// Search for modDate of the entity's attribute | ||
// and copy every attribute (if not in event yet) | ||
// for use in action template | ||
Object.keys(entity.attrs).forEach(function(attrName) { | ||
if (attrName === nsLineRule[ATTRIBUTE]) { | ||
if (!config.nonSignalByAPI) { | ||
// entity is really a entity doc obtained from mongo | ||
event.id = entity._id.id; | ||
event.type = entity._id.type; | ||
logger.debug(context, 'alertfunc event %j ', event); | ||
// Search for modDate of the entity's attribute | ||
// and copy every attribute (if not in event yet) | ||
// for use in action template | ||
Object.keys(entity.attrs).forEach(function(attrName) { | ||
if (attrName === nsLineRule[ATTRIBUTE]) { | ||
try { | ||
lastTime = new Date(entity.attrs[attrName].modDate * 1000).toISOString(); | ||
} catch (ex) { | ||
myutils.logErrorIf(ex, 'run ', d.context); | ||
} | ||
} | ||
if (event[attrName] === undefined) { | ||
if (entity.attrs[attrName].type === 'DateTime') { | ||
event[attrName] = new Date(entity.attrs[attrName].value * 1000).toISOString(); | ||
} else { | ||
event[attrName] = entity.attrs[attrName].value; | ||
} | ||
} | ||
}); | ||
} else { | ||
// entity is and NGSI object | ||
event.id = entity.id; | ||
event.type = entity.type; | ||
logger.debug(context, 'alertfunc event %j ', event); | ||
// Search for modDate of the entity's attribute | ||
// and copy every attribute (if not in event yet) | ||
// for use in action template | ||
const attrName = nsLineRule[ATTRIBUTE]; | ||
if (entity[attrName]) { | ||
try { | ||
lastTime = new Date(entity.attrs[attrName].modDate * 1000).toISOString(); | ||
lastTime = entity[attrName].metadata.TimeInstant.value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment for all: we are checking the attr metadata, but not the modDate... I'm not sure it is correct. Probably is the best we have, but not every attribute will be suitable for this mechanism. We've got an internal _modDate for the entity but no for the attr, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fermin clarifies that "modDate" is the built in metadata from MONGO/NGSI and not an arbitrary metadata. OK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As extra info: the name of the metadata is |
||
} catch (ex) { | ||
myutils.logErrorIf(ex, 'run ', d.context); | ||
} | ||
} | ||
if (event[attrName] === undefined) { | ||
if (entity.attrs[attrName].type === 'DateTime') { | ||
event[attrName] = new Date(entity.attrs[attrName].value * 1000).toISOString(); | ||
} else { | ||
event[attrName] = entity.attrs[attrName].value; | ||
if (event[attrName] === undefined) { | ||
if (entity[attrName].type === 'DateTime') { | ||
event[attrName] = entity[attrName].metadata.TimeInstant.value; | ||
} else { | ||
event[attrName] = entity[attrName].value; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
logger.debug(context, 'lastTime could be ', lastTime); | ||
if (lastTime !== undefined && lastTime !== null) { | ||
|
@@ -119,6 +146,7 @@ function checkNoSignal(period) { | |
currentContext.srv = 'n/a'; | ||
currentContext.subsrv = 'n/a'; | ||
logger.debug(currentContext, 'Executing no-signal handler for period of %d (%d rules)', period, list.length); | ||
|
||
list.forEach(function(nsrule) { | ||
currentContext.srv = nsrule[SERVICE]; | ||
currentContext.subsrv = nsrule[SUBSERVICE]; | ||
|
@@ -192,6 +220,7 @@ function addNSRule(service, subservice, name, nsr) { | |
); | ||
intervalAsNum = MIN_INTERVAL_MS; | ||
} | ||
|
||
arrayRule = nsr2arr(service, subservice, name, nsr); | ||
nsRulesByInterval[nsr.checkInterval] = nsRulesByInterval[nsr.checkInterval] || []; | ||
nsRulesByInterval[nsr.checkInterval].forEach(function(element, index, array) { | ||
|
@@ -206,6 +235,7 @@ function addNSRule(service, subservice, name, nsr) { | |
logger.debug(context, util.format('Adding no-signal rule (%s, %s, %s)', service, subservice, name)); | ||
} | ||
if (!checkers.hasOwnProperty(nsr.checkInterval)) { | ||
logger.info(context, util.format('no-signal rule (%s, %s, %s)', service, subservice, name)); | ||
checkers[nsr.checkInterval] = setInterval(checkNoSignal, intervalAsNum, nsr.checkInterval); | ||
checkers[nsr.checkInterval].unref(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U | ||
* | ||
* This file is part of perseo-fe | ||
* | ||
* perseo-fe 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. | ||
* | ||
* perseo-fe 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 perseo-fe. | ||
* If not, see http://www.gnu.org/licenses/. | ||
* | ||
* For those usages not covered by the GNU Affero General Public License | ||
* please contact with iot_support at tid dot es | ||
* | ||
* Created by: Carlos Blanco - Future Internet Consulting and Development Solutions (FICODES) | ||
*/ | ||
|
||
'use strict'; | ||
fgalan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var should = require('should'); | ||
var rewire = require('rewire'); | ||
var entitiesStore = rewire('../../lib/models/entitiesStore.js'); | ||
var chai = require('chai'); | ||
var sinon = require('sinon'); | ||
var sinonChai = require('sinon-chai'); | ||
var expect = chai.expect; | ||
var config = require('../../config.js'); | ||
var NGSI = require('ngsijs'); | ||
chai.Should(); | ||
chai.use(sinonChai); | ||
|
||
describe('entitiesStore', function() { | ||
describe('#findSilentEntitiesByAPI', function() { | ||
var connectionMock; | ||
var listEntitiesMock; | ||
|
||
beforeEach(function() { | ||
connectionMock = sinon.stub(NGSI, 'Connection'); | ||
listEntitiesMock = sinon.stub(); | ||
|
||
// Mock the Connection function | ||
connectionMock.returns({ v2: { listEntities: listEntitiesMock } }); | ||
}); | ||
|
||
afterEach(function() { | ||
// Restore the original function after each test | ||
connectionMock.restore(); | ||
}); | ||
|
||
var ruleData = { | ||
name: 'NSR2', | ||
action: { | ||
type: 'update', | ||
parameters: { | ||
id: 'alarma:${id}', | ||
type: 'Alarm', | ||
attributes: [ | ||
{ | ||
name: 'msg', | ||
value: 'El status de ${id} es ${status}' | ||
} | ||
] | ||
} | ||
}, | ||
subservice: '/', | ||
service: 'unknownt', | ||
nosignal: { | ||
checkInterval: '1', | ||
attribute: 'temperature', | ||
reportInterval: '5', | ||
id: 'thing:disp1', | ||
idRegexp: null, | ||
type: 'thing' | ||
} | ||
}, | ||
func = 'sinon.stub()', | ||
callback = function(e, request) { | ||
should.exist(request); | ||
should.not.exist(e); | ||
should.equal(request.httpCode, 200); | ||
}; | ||
|
||
it('By default should call findSilentEntitiesByMongo', function() { | ||
var findSilentEntitiesByMongoSpy = sinon.spy(); | ||
entitiesStore.__set__('findSilentEntitiesByMongo', findSilentEntitiesByMongoSpy); | ||
entitiesStore.FindSilentEntities(ruleData.service, ruleData.subservice, ruleData, func, callback); | ||
sinon.assert.calledOnce(findSilentEntitiesByMongoSpy); | ||
}); | ||
|
||
it('If default settings are changed FindSilentEntitiesByAPI should be called', function() { | ||
config.nonSignalByAPI = true; | ||
var findSilentEntitiesByAPISpy = sinon.spy(); | ||
entitiesStore.__set__('findSilentEntitiesByAPI', findSilentEntitiesByAPISpy); | ||
entitiesStore.FindSilentEntities(); | ||
sinon.assert.calledOnce(findSilentEntitiesByAPISpy); | ||
}); | ||
|
||
AlvaroVega marked this conversation as resolved.
Show resolved
Hide resolved
|
||
it('should return silent entities', async function() { | ||
var funcM = sinon.spy(), | ||
callbackM = sinon.spy(); | ||
|
||
// Mock the listEntities function to resolve with a test response | ||
listEntitiesMock.returns(Promise.resolve({ results: [] })); | ||
|
||
await entitiesStore.findSilentEntitiesByAPI( | ||
ruleData.service, | ||
ruleData.subservice, | ||
ruleData, | ||
funcM, | ||
callbackM | ||
); | ||
|
||
expect(listEntitiesMock.calledOnce).to.be.true; | ||
expect(funcM.callCount).to.equal(0); | ||
expect(callbackM.calledOnceWith(null, [])).to.be.true; | ||
}); | ||
}); | ||
AlvaroVega marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE-dependant files shouldn't appear in PRs.