diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 386c7df498..037e8147bc 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -4,6 +4,7 @@ - Fix: wrong date values should not allowed in subscription's expires field (#4541) - Fix: do not raise DB alarm in case of wrong GeoJSON in client request - Fix: registrations with more tha one contextRegistration element (not possible in NGSIv2) are logged as Runtime Errors +- Fix: metadata modifications are not considered as change (with regards to subscription alterationTypes) if notifyOnMetadataChange is false (#4605) - Upgrade cjexl version from 0.3.0 to 0.4.0 (new transformations: now, getTime and toIsoString) - Upgrade Debian version from 12.4 to 12.6 in Dockerfile - Fix: invalid date in expires field of subscription (#2303) diff --git a/README.md b/README.md index c3a2834151..61f25363ff 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ recommended to have a look to the brief ### Introductory presentations - Orion Context Broker - [(en)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240604/269503234) + [(en)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240911/271735060) [(jp)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240605/269515246) - NGSIv2 Overview for Developers That Already Know NGSIv1 [(en)](https://www.slideshare.net/fermingalan/orion-context-broker-ngsiv2-overview-for-developers-that-already-know-ngsiv1-20220523) diff --git a/doc/manuals.jp/devel/README.md b/doc/manuals.jp/devel/README.md index bfbf13e1d1..b1e9228816 100644 --- a/doc/manuals.jp/devel/README.md +++ b/doc/manuals.jp/devel/README.md @@ -1,6 +1,6 @@ # 開発マニュアル -*注 : このドキュメントでは、リリース 4.0.x の Orion Context Broker について説明しています。* +*注 : このドキュメントでは、リリース 4.1.x の Orion Context Broker について説明しています。* ## 対象読者 diff --git a/doc/manuals/devel/README.md b/doc/manuals/devel/README.md index dd6d690c79..3f7f9c0af0 100644 --- a/doc/manuals/devel/README.md +++ b/doc/manuals/devel/README.md @@ -1,6 +1,6 @@ # Development Manual -*Note: This document describes Orion Context Broker as of release 4.0.x.* +*Note: This document describes Orion Context Broker as of release 4.1.x.* ## Intended audience The intended audience of this manual is developers that need to understand the internals of the Orion Context Broker diff --git a/src/lib/apiTypesV2/Subscription.cpp b/src/lib/apiTypesV2/Subscription.cpp index 254bb4fecd..564237f480 100644 --- a/src/lib/apiTypesV2/Subscription.cpp +++ b/src/lib/apiTypesV2/Subscription.cpp @@ -42,7 +42,7 @@ ngsiv2::SubAltType parseAlterationType(const std::string& altType) { if (altType == "entityChange") { - return ngsiv2::SubAltType::EntityChange; + return ngsiv2::SubAltType::EntityChangeBothValueAndMetadata; } else if (altType == "entityUpdate") { @@ -64,13 +64,26 @@ ngsiv2::SubAltType parseAlterationType(const std::string& altType) +/* **************************************************************************** +* +* isChangeAltType - +*/ +bool isChangeAltType(ngsiv2::SubAltType altType) +{ + return (altType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) || + (altType == ngsiv2::SubAltType::EntityChangeOnlyMetadata) || + (altType == ngsiv2::SubAltType::EntityChangeOnlyValue); +} + + + /* **************************************************************************** * * subAltType2string - */ std::string subAltType2string(ngsiv2::SubAltType altType) { - if (altType == ngsiv2::SubAltType::EntityChange) + if (isChangeAltType(altType)) { return "entityChange"; } diff --git a/src/lib/apiTypesV2/Subscription.h b/src/lib/apiTypesV2/Subscription.h index 1a97fd57fe..7ca649dde6 100644 --- a/src/lib/apiTypesV2/Subscription.h +++ b/src/lib/apiTypesV2/Subscription.h @@ -55,7 +55,11 @@ typedef enum NotificationType */ typedef enum SubAltType { - EntityChange, + // EntityChange has been specialized into three sub-types in order to solve #4605 + // (EntityChangeBothValueAndMetadata is thre reference one used in parsing/rendering logic) + EntityChangeBothValueAndMetadata, + EntityChangeOnlyValue, + EntityChangeOnlyMetadata, EntityUpdate, EntityCreate, EntityDelete, @@ -190,4 +194,12 @@ extern std::string subAltType2string(ngsiv2::SubAltType altType); +/* **************************************************************************** +* +* isChangeAltType - +*/ +extern bool isChangeAltType(ngsiv2::SubAltType altType); + + + #endif // SRC_LIB_APITYPESV2_SUBSCRIPTION_H_ diff --git a/src/lib/cache/subCache.cpp b/src/lib/cache/subCache.cpp index 2074fbc4fa..53d29aca1c 100644 --- a/src/lib/cache/subCache.cpp +++ b/src/lib/cache/subCache.cpp @@ -400,7 +400,7 @@ static bool matchAltType(CachedSubscription* cSubP, ngsiv2::SubAltType targetAlt // If subAltTypeV size == 0 default alteration types are update with change and create if (cSubP->subAltTypeV.size() == 0) { - if ((targetAltType == ngsiv2::SubAltType::EntityChange) || (targetAltType == ngsiv2::SubAltType::EntityCreate)) + if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::SubAltType::EntityCreate)) { return true; } @@ -414,9 +414,9 @@ static bool matchAltType(CachedSubscription* cSubP, ngsiv2::SubAltType targetAlt ngsiv2::SubAltType altType = cSubP->subAltTypeV[ix]; // EntityUpdate is special, it is a "sub-type" of EntityChange - if (targetAltType == ngsiv2::SubAltType::EntityChange) + if (isChangeAltType(targetAltType)) { - if ((altType == ngsiv2::SubAltType::EntityUpdate) || (altType == ngsiv2::SubAltType::EntityChange)) + if ((altType == ngsiv2::SubAltType::EntityUpdate) || (isChangeAltType(altType))) { return true; } @@ -449,6 +449,12 @@ static bool subMatch ngsiv2::SubAltType targetAltType ) { + // If notifyOnMetadataChange is false and only metadata has been changed, we "downgrade" to ngsiv2::EntityUpdate + if (!cSubP->notifyOnMetadataChange && (targetAltType == ngsiv2::EntityChangeOnlyMetadata)) + { + targetAltType = ngsiv2::EntityUpdate; + } + // Check alteration type if (!matchAltType(cSubP, targetAltType)) { @@ -487,7 +493,6 @@ static bool subMatch return false; } - // // If one of the attribute names in the scope vector // of the subscription has the same name as the incoming attribute. there is a match. @@ -504,10 +509,13 @@ static bool subMatch return false; } } - else if ((targetAltType == ngsiv2::EntityChange) || (targetAltType == ngsiv2::EntityCreate)) + else if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::EntityCreate)) { - if (!attributeMatch(cSubP, attrsWithModifiedValue) && - !(cSubP->notifyOnMetadataChange && attributeMatch(cSubP, attrsWithModifiedMd))) + // No match if: 1) there is no change in the *value* of attributes listed in conditions.attrs and 2) there is no change + // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadataChange is true) + bool b1 = attributeMatch(cSubP, attrsWithModifiedValue); + bool b2 = attributeMatch(cSubP, attrsWithModifiedMd); + if (!b1 && !(cSubP->notifyOnMetadataChange && b2)) { LM_T(LmtSubCacheMatch, ("No match due to attributes")); return false; diff --git a/src/lib/mongoBackend/MongoCommonUpdate.cpp b/src/lib/mongoBackend/MongoCommonUpdate.cpp index 24def43268..04c17fced6 100644 --- a/src/lib/mongoBackend/MongoCommonUpdate.cpp +++ b/src/lib/mongoBackend/MongoCommonUpdate.cpp @@ -1490,7 +1490,7 @@ static bool matchAltType(orion::BSONObj sub, ngsiv2::SubAltType targetAltType) // change and create. Maybe this could be check at MongoDB query stage, but seems be more complex if (altTypeStrings.size() == 0) { - if ((targetAltType == ngsiv2::SubAltType::EntityChange) || (targetAltType == ngsiv2::SubAltType::EntityCreate)) + if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::SubAltType::EntityCreate)) { return true; } @@ -1510,9 +1510,9 @@ static bool matchAltType(orion::BSONObj sub, ngsiv2::SubAltType targetAltType) else { // EntityUpdate is special, it is a "sub-type" of EntityChange - if (targetAltType == ngsiv2::SubAltType::EntityChange) + if (isChangeAltType(targetAltType)) { - if ((altType == ngsiv2::SubAltType::EntityUpdate) || (altType == ngsiv2::SubAltType::EntityChange)) + if ((altType == ngsiv2::SubAltType::EntityUpdate) || (isChangeAltType(altType))) { return true; } @@ -1638,15 +1638,21 @@ static bool addTriggeredSubscriptions_noCache if (subs.count(subIdStr) == 0) { + // Early extraction of fiedl from DB document. The rest of fields are got later + bool notifyOnMetadataChange = sub.hasField(CSUB_NOTIFYONMETADATACHANGE)? getBoolFieldF(sub, CSUB_NOTIFYONMETADATACHANGE) : true; + + // If notifyOnMetadataChange is false and only metadata has been changed, we "downgrade" to ngsiv2::EntityUpdate + if (!notifyOnMetadataChange && (targetAltType == ngsiv2::EntityChangeOnlyMetadata)) + { + targetAltType = ngsiv2::EntityUpdate; + } + // Check alteration type if (!matchAltType(sub, targetAltType)) { continue; } - // Early extraction of fiedl from DB document. The rest of fields are got later - bool notifyOnMetadataChange = sub.hasField(CSUB_NOTIFYONMETADATACHANGE)? getBoolFieldF(sub, CSUB_NOTIFYONMETADATACHANGE) : true; - // Depending of the alteration type, we use the list of attributes in the request or the list // with effective modifications. Note that EntityDelete doesn't check the list if (targetAltType == ngsiv2::EntityUpdate) @@ -1656,11 +1662,14 @@ static bool addTriggeredSubscriptions_noCache continue; } } - else if ((targetAltType == ngsiv2::EntityChange) || (targetAltType == ngsiv2::EntityCreate)) + else if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::EntityCreate)) { // Skip if: 1) there is no change in the *value* of attributes listed in conditions.attrs and 2) there is no change - // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadtaChange is true) - if (!condValueAttrMatch(sub, attrsWithModifiedValue) && !(notifyOnMetadataChange && condValueAttrMatch(sub, attrsWithModifiedMd))) + // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadataChange is true) + bool b1 = condValueAttrMatch(sub, attrsWithModifiedValue); + bool b2 = condValueAttrMatch(sub, attrsWithModifiedMd); + + if (!b1 && !(notifyOnMetadataChange && b2)) { continue; } @@ -2722,24 +2731,34 @@ static bool processContextAttributeVector { attrsWithModifiedValue.push_back(ca->name); attrsWithModifiedMd.push_back(ca->name); + targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata; } else if (changeType == CHANGE_ONLY_VALUE) { attrsWithModifiedValue.push_back(ca->name); + if ((targetAltType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) || (targetAltType == ngsiv2::SubAltType::EntityChangeOnlyMetadata)) + { + targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata; + } + else + { + targetAltType = ngsiv2::SubAltType::EntityChangeOnlyValue; + } } else if (changeType == CHANGE_ONLY_MD) { attrsWithModifiedMd.push_back(ca->name); + if ((targetAltType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) || (targetAltType == ngsiv2::SubAltType::EntityChangeOnlyValue)) + { + targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata; + } + else + { + targetAltType = ngsiv2::SubAltType::EntityChangeOnlyMetadata; + } } attributes.push_back(ca->name); - - /* If actual update then targetAltType changes from EntityUpdate (the value used to initialize - * the variable) to EntityChange */ - if (changeType != NO_CHANGE) - { - targetAltType = ngsiv2::SubAltType::EntityChange; - } } /* Add triggered subscriptions */ @@ -3895,7 +3914,7 @@ static unsigned int updateEntity /* Send notifications for each one of the subscriptions accumulated by * previous addTriggeredSubscriptions() invocations. Before that, we add * builtin attributes and metadata */ - addBuiltins(notifyCerP, subAltType2string(ngsiv2::SubAltType::EntityChange)); + addBuiltins(notifyCerP, subAltType2string(ngsiv2::SubAltType::EntityChangeBothValueAndMetadata)); unsigned int notifSent = processSubscriptions(subsToNotify, notifyCerP, tenant, diff --git a/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test b/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test new file mode 100644 index 0000000000..bbfb1f476a --- /dev/null +++ b/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test @@ -0,0 +1,333 @@ +# Copyright 2024 Telefonica Investigacion y Desarrollo, S.A.U +# +# This file is part of Orion Context Broker. +# +# Orion Context Broker 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. +# +# Orion Context Broker 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 Orion Context Broker. If not, see http://www.gnu.org/licenses/. +# +# For those usages not covered by this license please contact with +# iot_support at tid dot es + +# VALGRIND_READY - to mark the test ready for valgrindTestSuite.sh + +--NAME-- +alterationType entityUpdate in combination with notifyOnMetadataChange + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false +# 02. Create entity dummy-device-01 with two attributes +# 03. Update entity dummy-device-01 metadata in two attributes +# 04. Update entity dummy-device-01 without actual update +# 05. Dump accumulator and see three notifications +# + + +echo "01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false" +echo "=============================================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern": ".*", + "type": "DummyDevice" + } + ], + "condition": { + "attrs": [ + "temperature", + "humidity" + ], + "notifyOnMetadataChange": false, + "alterationTypes": [ + "entityUpdate", + "entityCreate" + ] + } + }, + "notification": { + "attrs": [ + "temperature", + "humidity" + ], + "onlyChangedAttrs": true, + "attrsFormat": "normalized", + "http": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify" + }, + "metadata": [ + "TimeInstant" + ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity dummy-device-01 with two attributes" +echo "=====================================================" +payload=' +{ + "id": "dummy-device-01", + "type": "DummyDevice", + "temperature": { + "type": "Number", + "value": 20, + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:00:00.000Z" + } + } + }, + "humidity": { + "type": "Number", + "value": 50, + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:00:00.000Z" + } + } + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity dummy-device-01 metadata in two attributes" +echo "============================================================" +payload='{ + "temperature": { + "type": "Number", + "value": 20, + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + } + }, + "humidity": { + "type": "Number", + "value": 50, + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + } + } +}' +orionCurl --url /v2/entities/dummy-device-01/attrs -X POST --payload "$payload" +echo +echo + + +echo "04. Update entity dummy-device-01 without actual update" +echo "=======================================================" +payload='{ + "temperature": { + "type": "Number", + "value": 20 + }, + "humidity": { + "type": "Number", + "value": 50 + } +}' +orionCurl --url /v2/entities/dummy-device-01/attrs -X POST --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see three notifications" +echo "================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false +============================================================================================= +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/subscriptions/REGEX([0-9a-f]{24}) +Content-Length: 0 + + + +02. Create entity dummy-device-01 with two attributes +===================================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/dummy-device-01?type=DummyDevice +Content-Length: 0 + + + +03. Update entity dummy-device-01 metadata in two attributes +============================================================ +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Update entity dummy-device-01 without actual update +======================================================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +05. Dump accumulator and see three notifications +================================================ +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 347 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: normalized +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: application/json; charset=utf-8 +Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 + +{ + "data": [ + { + "humidity": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:00:00.000Z" + } + }, + "type": "Number", + "value": 50 + }, + "id": "dummy-device-01", + "temperature": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:00:00.000Z" + } + }, + "type": "Number", + "value": 20 + }, + "type": "DummyDevice" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 347 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: normalized +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: application/json; charset=utf-8 +Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 + +{ + "data": [ + { + "humidity": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + }, + "type": "Number", + "value": 50 + }, + "id": "dummy-device-01", + "temperature": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + }, + "type": "Number", + "value": 20 + }, + "type": "DummyDevice" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 347 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: normalized +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: application/json; charset=utf-8 +Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 + +{ + "data": [ + { + "humidity": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + }, + "type": "Number", + "value": 50 + }, + "id": "dummy-device-01", + "temperature": { + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2024-08-20T00:10:00.000Z" + } + }, + "type": "Number", + "value": 20 + }, + "type": "DummyDevice" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop