Skip to content
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

Merged
merged 87 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
46d4bf3
split findSilentEntities by mongo or API
AlvaroVega May 30, 2023
cea4ca5
add logs and flag config for check non signal by API
AlvaroVega Jun 5, 2023
693a388
update filter with params for listEntity of ngsijs
AlvaroVega Jun 6, 2023
3f87d0e
add ruleData to log about rule
AlvaroVega Jun 6, 2023
08ca5db
fix dateModified usage in filter of entites
AlvaroVega Jun 6, 2023
ae7708d
fix dateModified and func by entity
AlvaroVega Jun 8, 2023
2071248
add expected _id subdoc to entity
AlvaroVega Jun 9, 2023
b9a40f2
update alterfunc to allow config.nonSignalByAPI
AlvaroVega Jun 9, 2023
df171c0
change definition order
RafaelM3 Jun 12, 2023
0ac60c9
findSilentEntities test
RafaelM3 Jun 20, 2023
c046597
findSilentEntities test
RafaelM3 Jun 20, 2023
82a4910
correcting linter issues
RafaelM3 Jun 21, 2023
801fa80
sugestions and improvements
RafaelM3 Jun 21, 2023
b7168c1
u test and linter problems correction
RafaelM3 Jun 22, 2023
dcf84dd
linter correction
RafaelM3 Jun 22, 2023
f71575e
findSilentEntitiesByAPI test
RafaelM3 Jun 26, 2023
b0958b9
linter problems
RafaelM3 Jun 26, 2023
cabcd0d
linter skip line
RafaelM3 Jun 26, 2023
4fd3bf0
linter skip line
RafaelM3 Jun 26, 2023
b3ef4fe
linter, skip section
RafaelM3 Jun 26, 2023
3cdf6ce
skip section
RafaelM3 Jun 26, 2023
c88639f
linter, suggested changes
RafaelM3 Jun 26, 2023
d2038d2
unmatch {
RafaelM3 Jun 26, 2023
18c2c89
CNR and eliminate uncompleted test
RafaelM3 Jun 27, 2023
bb3f154
removing unused rule
RafaelM3 Jun 27, 2023
a289a3d
Update configuration.md
RafaelM3 Jun 27, 2023
c036794
Update configuration.md
RafaelM3 Jun 27, 2023
184f6b7
small impact suggested changes
RafaelM3 Jun 27, 2023
164fb54
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jun 27, 2023
b0ba12e
Update configuration.md
RafaelM3 Jun 27, 2023
cb70ed8
findSilentEntitiesByAPIWithPagination
RafaelM3 Jun 28, 2023
87c1766
Merge branch 'master' into task/non_signal_by_api
RafaelM3 Jun 28, 2023
04543fa
createConnection test
RafaelM3 Jun 28, 2023
b251606
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jun 28, 2023
7f16995
findSilentEntitiesByAPI test
RafaelM3 Jun 28, 2023
aa79f86
changing problematic definitions
RafaelM3 Jun 28, 2023
4b63d3d
unused reference
RafaelM3 Jun 28, 2023
a0b86c3
renaming unclear function
RafaelM3 Jun 29, 2023
e593c4c
erease bad test
RafaelM3 Jun 29, 2023
543cfdd
clarifying the code
RafaelM3 Jun 29, 2023
cb84048
create filter test
RafaelM3 Jun 29, 2023
ebdd267
findsilententites timmer update
RafaelM3 Jun 30, 2023
145e798
finsilententites timmer fix
RafaelM3 Jun 30, 2023
92aa5fe
update findSilentEntities
RafaelM3 Jun 30, 2023
c513522
findSilentEntities total count fix
RafaelM3 Jun 30, 2023
3020ed0
context fix
RafaelM3 Jun 30, 2023
2f53ae4
Merge pull request #730 from telefonicaid/task/find_silent_entities_t…
RafaelM3 Jun 30, 2023
38a2932
findSilentEntities timmer fix
RafaelM3 Jun 30, 2023
53f1a47
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jun 30, 2023
e240874
count entities fix
RafaelM3 Jul 3, 2023
897e205
Merge branch 'master' into task/non_signal_by_api
RafaelM3 Jul 3, 2023
2369f6b
Update CHANGES_NEXT_RELEASE
RafaelM3 Jul 3, 2023
f3207ec
count entities fix
RafaelM3 Jul 3, 2023
00dcd96
test fix
RafaelM3 Jul 3, 2023
b2e11dd
context fix
RafaelM3 Jul 4, 2023
855cde0
context fix
RafaelM3 Jul 4, 2023
53796c1
count entities fix
RafaelM3 Jul 4, 2023
3f3b8ef
debug fix
RafaelM3 Jul 4, 2023
06d20d8
info fix
RafaelM3 Jul 4, 2023
8613403
test update
RafaelM3 Jul 4, 2023
7c6bcc8
test fix
RafaelM3 Jul 4, 2023
d939e62
utest fix
RafaelM3 Jul 5, 2023
6cd8488
createConnection update
RafaelM3 Jul 5, 2023
4086d69
createFilter update
RafaelM3 Jul 5, 2023
b1808b2
Update docs/admin/configuration.md
fgalan Jul 5, 2023
249a179
Update package.json
fgalan Jul 5, 2023
023724b
Update package.json
fgalan Jul 5, 2023
5e55352
Update package.json
fgalan Jul 5, 2023
b1ac552
Update package.json
fgalan Jul 5, 2023
43501ba
Update test/unit/entitiesStore_utest.js
fgalan Jul 5, 2023
790b9a6
Update lib/models/entitiesStore.js
RafaelM3 Jul 5, 2023
77dd194
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jul 5, 2023
ba8cd92
non_singal update
RafaelM3 Jul 5, 2023
7d86c11
test fix
RafaelM3 Jul 5, 2023
860d818
delete unwanted file
RafaelM3 Jul 5, 2023
9c6338f
Update CHANGES_NEXT_RELEASE
fgalan Jul 5, 2023
ffa6fb8
createConnection update
RafaelM3 Jul 5, 2023
c08ca19
Update entitiesStore.js
RafaelM3 Jul 5, 2023
f68a9df
countEntitieByMongo fix
RafaelM3 Jul 6, 2023
927f42d
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jul 6, 2023
241a7a7
pagination update
RafaelM3 Jul 6, 2023
eac5024
entityCount
RafaelM3 Jul 7, 2023
aa3a6e4
findSilentEntitiesByAPIWithPagination test update
RafaelM3 Jul 7, 2023
05c0ef5
test update
RafaelM3 Jul 7, 2023
480d7cd
Merge branch 'master' into task/non_signal_by_api
RafaelM3 Jul 7, 2023
3eac6d4
findSilentEntitiesByMongo test update
RafaelM3 Jul 7, 2023
fe27602
Merge branch 'task/non_signal_by_api' of https://github.com/telefonic…
RafaelM3 Jul 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .vscode/settings.json
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs to be removed from PR.

Empty file.
6 changes: 5 additions & 1 deletion bin/perseo
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ function loadConfiguration() {
'PERSEO_AUTHENTICATION_USER',
'PERSEO_AUTHENTICATION_PASSWORD',
'PERSEO_AUTHENTICATION_SERVICE',
'PERSEO_MAX_RULES_BY_CORR'
'PERSEO_MAX_RULES_BY_CORR',
'PERSEO_CHECK_NON_SIGNAL_BY_API'
];

const protectedVariables = [
Expand Down Expand Up @@ -234,6 +235,9 @@ function loadConfiguration() {
if (process.env.PERSEO_MAX_RULES_BY_CORR) {
config.maxRulesByCorr = process.env.PERSEO_MAX_RULES_BY_CORR;
}
if (process.env.PERSEO_CHECK_NON_SIGNAL_BY_API) {
config.nonSignalByAPI = process.env.PERSEO_CHECK_NON_SIGNAL_BY_API;
}
}

loadConfiguration();
Expand Down
6 changes: 6 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,10 @@ config.castTypes = false;
*/
config.maxRulesByCorr = 20;

/**
* Check nonSignal rules using API ngsiv2 or just access to mongo
* @type {Boolean}
*/
config.nonSignalByAPI = false;

module.exports = config;
94 changes: 88 additions & 6 deletions lib/models/entitiesStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Copy link
Member

Choose a reason for hiding this comment

The 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 :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved in this commit:
184f6b7


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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary to catch any error? are they managed?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would preffer INFO level

Copy link
Collaborator

Choose a reason for hiding this comment

The 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(
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func has no meaning. Use some meaningful identifier for "func" param

Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
Expand Down Expand Up @@ -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;
62 changes: 46 additions & 16 deletions lib/models/noSignal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} 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) {
Expand All @@ -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];
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}
Expand Down
126 changes: 126 additions & 0 deletions test/unit/entitiesStore_utest.js
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
});
Loading