From 2b7798da02bc6da84b551e05d32c318fc0da3e70 Mon Sep 17 00:00:00 2001 From: Fermin Galan Marquez Date: Thu, 9 May 2019 20:12:22 +0200 Subject: [PATCH] FIX partial porting of Ken's PR #3480 --- src/lib/mongoBackend/MongoGlobal.cpp | 190 ++++++++++---- src/lib/mongoBackend/MongoGlobal.h | 3 +- src/lib/ngsi10/UpdateContextRequest.cpp | 13 +- src/lib/ngsi10/UpdateContextRequest.h | 3 +- src/lib/ngsi10/UpdateContextRequest.h.orig | 97 ++++++++ src/lib/serviceRoutines/postUpdateContext.cpp | 233 ++++++++++++------ .../forwards_with_accumulator.test | 1 + test/functionalTest/harnessFunctions.sh | 10 +- 8 files changed, 410 insertions(+), 140 deletions(-) create mode 100644 src/lib/ngsi10/UpdateContextRequest.h.orig diff --git a/src/lib/mongoBackend/MongoGlobal.cpp b/src/lib/mongoBackend/MongoGlobal.cpp index 70e61f7d4b..cc0ee4a64a 100644 --- a/src/lib/mongoBackend/MongoGlobal.cpp +++ b/src/lib/mongoBackend/MongoGlobal.cpp @@ -772,7 +772,16 @@ bool includedEntity(EntityId en, const EntityIdVector& entityIdV) { for (unsigned int ix = 0; ix < entityIdV.size(); ++ix) { - if (matchEntity(&en, entityIdV[ix])) + if (en.isPatternIsTrue() && en.id == ".*") + { + // By the moment the only supported pattern is .*. In this case matching is + // based exclusively in type + if (en.type == entityIdV[ix]->type) + { + return true; + } + } + else if (matchEntity(&en, entityIdV[ix])) { return true; } @@ -2004,51 +2013,118 @@ bool registrationsQuery long long* countP ) { - /* Build query based on arguments */ - // FIXME P2: this implementation needs to be refactored for cleanup - std::string contextRegistrationEntities = REG_CONTEXT_REGISTRATION "." REG_ENTITIES; - std::string contextRegistrationEntitiesId = REG_CONTEXT_REGISTRATION "." REG_ENTITIES "." REG_ENTITY_ID; - std::string contextRegistrationEntitiesType = REG_CONTEXT_REGISTRATION "." REG_ENTITIES "." REG_ENTITY_TYPE; - std::string contextRegistrationAttrsNames = REG_CONTEXT_REGISTRATION "." REG_ATTRS "." REG_ATTRS_NAME; - BSONArrayBuilder entityOr; - BSONArrayBuilder entitiesWithType; - BSONArrayBuilder entitiesWithoutType; + mongo::BSONObjBuilder queryBuilder; + mongo::BSONArrayBuilder entityOr; - for (unsigned int ix = 0; ix < enV.size(); ++ix) + // + // If there's only one entity in the REST request, the query to mongo can be simplified. + // NGSIv2 requests like PATCH /v2/entities//attrs can have only ONE entity + // and will ALWAYS enter the "then part" of the if-then-else. + // + // FIXME: Note than registrations with entity id as a pattern is treated by the "then part" but not by the older "else part". + // When forwarding for batch update is implemented this needs to be fixed, somehow + // Once NGSIv1 is removed from the source code, the "else part" can be removed by just making sure that + // NGSIv2 batch operations call this function once per entity in the request (modifications needed). + // Or, we'd need to reimplement to make the else-part also support registrations with entity id as a pattern. + // + // Note by fgalan: actually in the entity update logic enV has always one element, by construction (remember that + // update logic processes entities one-by-one). In the case of entity query logic I guess that we have cases in + // which enV can have more than one element. + // + // FIXME PR: to review above text before merging + // + if (enV.size() == 1) { - const EntityId* en = enV[ix]; + if ((enV[0]->id == "") && (enV[0]->type == "")) + { + LM_E(("Bad Request (too wide query - at least one of entity::id and entity::type must be given)")); + *err = "too wide query"; + return false; + } - if (isTrue(en->isPattern)) + // Entity ID - if not given, no Entity ID in query - then ALL ENTITY IDs match, so not included in query + if (enV[0]->id != "") { - BSONObjBuilder b; + mongo::BSONObjBuilder entityAnd; + mongo::BSONObjBuilder id; - b.appendRegex(contextRegistrationEntitiesId, en->id); - if (en->type != "") - { - b.append(contextRegistrationEntitiesType, en->type); - } - entityOr.append(b.obj()); + // If the Registration is with idPattern, then it matches, as the only idPattern allowed for Registrations is ".*" + entityAnd.append("contextRegistration.entities.isPattern", "true"); + entityAnd.append("contextRegistration.entities.id", ".*"); // FIXME TPUT: if isPattern == true, then id MUST be ".*" ! Remove? + entityOr.append(entityAnd.obj()); + + if (enV[0]->isPattern == "true") id.appendRegex("contextRegistration.entities.id", enV[0]->id); + else id.append("contextRegistration.entities.id", enV[0]->id); + + entityOr.append(id.obj()); + queryBuilder.append("$or", entityOr.arr()); + } + + // Entity Type + if (enV[0]->type != "") + { + queryBuilder.append("contextRegistration.entities.type", enV[0]->type); } - else /* isPattern = false */ + } + else // More than one Entity in enV + { + /* Build query based on arguments */ + // FIXME P2: this implementation needs to be refactored for cleanup + std::string contextRegistrationEntities = REG_CONTEXT_REGISTRATION "." REG_ENTITIES; + std::string contextRegistrationEntitiesId = REG_CONTEXT_REGISTRATION "." REG_ENTITIES "." REG_ENTITY_ID; + std::string contextRegistrationEntitiesType = REG_CONTEXT_REGISTRATION "." REG_ENTITIES "." REG_ENTITY_TYPE; + BSONArrayBuilder entitiesWithType; + BSONArrayBuilder entitiesWithoutType; + + for (unsigned int ix = 0; ix < enV.size(); ++ix) { - if (en->type == "") + const EntityId* en = enV[ix]; + + if (isTrue(en->isPattern)) { - entitiesWithoutType.append(en->id); - LM_T(LmtMongo, ("Entity discovery without type: id '%s'", en->id.c_str())); + BSONObjBuilder b; + + b.appendRegex(contextRegistrationEntitiesId, en->id); + if (en->type != "") + { + b.append(contextRegistrationEntitiesType, en->type); + } + entityOr.append(b.obj()); } - else + else /* isPattern = false */ { - /* We have detected that sometimes mongo stores { id: ..., type ...} and sometimes { type: ..., id: ...}, - so we have to take both of them into account - */ - entitiesWithType.append(BSON(REG_ENTITY_ID << en->id << REG_ENTITY_TYPE << en->type)); - entitiesWithType.append(BSON(REG_ENTITY_TYPE << en->type << REG_ENTITY_ID << en->id)); - LM_T(LmtMongo, ("Entity discovery: {id: %s, type: %s}", en->id.c_str(), en->type.c_str())); + if (en->type == "") + { + entitiesWithoutType.append(BSON(REG_ENTITY_ISPATTERN << "true")); + entitiesWithoutType.append(BSON(REG_ENTITY_ID << en->id)); + LM_T(LmtMongo, ("Entity discovery without type: id '%s'", en->id.c_str())); + } + else + { + /* We have detected that sometimes mongo stores { id: ..., type ...} and sometimes { type: ..., id: ...}, + so we have to take both of them into account + */ + entitiesWithType.append(BSON(REG_ENTITY_ID << en->id << REG_ENTITY_TYPE << en->type)); + entitiesWithType.append(BSON(REG_ENTITY_TYPE << en->type << REG_ENTITY_ID << en->id)); + LM_T(LmtMongo, ("Entity discovery: {id: %s, type: %s}", en->id.c_str(), en->type.c_str())); + } } } + + entityOr.append(BSON(contextRegistrationEntities << BSON("$in" << entitiesWithType.arr()))); + entityOr.append(BSON(contextRegistrationEntitiesId << BSON("$in" << entitiesWithoutType.arr()))); + + /* The $or clause could be omitted if it contains only one element, but we can assume that + * it has no impact on MongoDB query optimizer + */ + queryBuilder.append("$or", entityOr.arr()); } + // + // Attributes + // BSONArrayBuilder attrs; + std::string contextRegistrationAttrsNames = REG_CONTEXT_REGISTRATION "." REG_ATTRS "." REG_ATTRS_NAME; for (unsigned int ix = 0; ix < attrL.size(); ++ix) { @@ -2058,17 +2134,6 @@ bool registrationsQuery LM_T(LmtMongo, ("Attribute discovery: '%s'", attrName.c_str())); } - entityOr.append(BSON(contextRegistrationEntities << BSON("$in" << entitiesWithType.arr()))); - entityOr.append(BSON(contextRegistrationEntitiesId << BSON("$in" << entitiesWithoutType.arr()))); - - BSONObjBuilder queryBuilder; - - /* The $or clause could be omitted if it contains only one element, but we can assume that - * it has no impact on MongoDB query optimizer - */ - queryBuilder.append("$or", entityOr.arr()); - queryBuilder.append(REG_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); - if (attrs.arrSize() > 0) { /* If we don't do this check, the {$in: [] } of the attribute name part makes the query fail */ @@ -2076,11 +2141,14 @@ bool registrationsQuery } // - // 'And-in' the service path + // Service Path // - const std::string servicePathString = REG_SERVICE_PATH; + queryBuilder.append(REG_SERVICE_PATH, fillQueryServicePath(servicePathV)); - queryBuilder.append(servicePathString, fillQueryServicePath(servicePathV)); + // + // Expiration + // + queryBuilder.append(REG_EXPIRATION, BSON("$gt" << (long long) getCurrentTime())); // @@ -2128,11 +2196,13 @@ bool registrationsQuery std::string format = getStringFieldF(r, REG_FORMAT); ProviderFormat providerFormat = (format == "")? PfJson : (format == "JSON")? PfJson : PfV2; + LM_T(LmtForward, ("--------------- %d registration elements to process", queryContextRegistrationV.size())); for (unsigned int ix = 0 ; ix < queryContextRegistrationV.size(); ++ix) { LM_T(LmtForward, ("Processing ContextRegistrationElement. providerFormat == '%s' (%d)", format.c_str(), providerFormat)); processContextRegistrationElement(queryContextRegistrationV[ix].embeddedObject(), enV, attrL, crrV, mimeType, providerFormat); } + LM_T(LmtForward, ("--------------- %d registration elements processed - %d elementd output in crrV", queryContextRegistrationV.size(), crrV->size())); /* FIXME: note that given the response doesn't distinguish from which registration ID the * response comes, it could have that we have same context registration elements, belong to different @@ -2765,10 +2835,11 @@ void fillContextProviders(ContextElementResponse* cer, const ContextRegistration } /* Search for some CPr in crrV */ - std::string perEntPa; - std::string perAttrPa; - MimeType perEntPaMimeType = NOMIMETYPE; - MimeType perAttrPaMimeType = NOMIMETYPE; + std::string perEntPa; + std::string perAttrPa; + MimeType perEntPaMimeType = NOMIMETYPE; + MimeType perAttrPaMimeType = NOMIMETYPE; + ProviderFormat providerFormat; cprLookupByAttribute(cer->entity, ca->name, @@ -2776,10 +2847,12 @@ void fillContextProviders(ContextElementResponse* cer, const ContextRegistration &perEntPa, &perEntPaMimeType, &perAttrPa, - &perAttrPaMimeType); + &perAttrPaMimeType, + &providerFormat); /* Looking results after crrV processing */ ca->providingApplication.set(perAttrPa == "" ? perEntPa : perAttrPa); + ca->providingApplication.setProviderFormat(providerFormat); ca->found = (ca->providingApplication.get() != ""); } } @@ -2826,7 +2899,8 @@ void cprLookupByAttribute std::string* perEntPa, MimeType* perEntPaMimeType, std::string* perAttrPa, - MimeType* perAttrPaMimeType + MimeType* perAttrPaMimeType, + ProviderFormat* providerFormatP ) { *perEntPa = ""; @@ -2841,7 +2915,17 @@ void cprLookupByAttribute { EntityId* regEn = crr->contextRegistration.entityIdVector[enIx]; - if (regEn->id != en.id || (regEn->type != en.type && regEn->type != "")) + if (regEn->isPatternIsTrue() && regEn->id == ".*") + { + // By the moment the only supported pattern is .*. In this case matching is + // based exclusively in type + if (regEn->type != en.type && regEn->type != "") + { + /* No match (keep searching the CRR) */ + continue; + } + } + else if (regEn->id != en.id || (regEn->type != en.type && regEn->type != "")) { /* No match (keep searching the CRR) */ continue; @@ -2851,6 +2935,7 @@ void cprLookupByAttribute if (crr->contextRegistration.contextRegistrationAttributeVector.size() == 0) { *perEntPa = crr->contextRegistration.providingApplication.get(); + *providerFormatP = crr->contextRegistration.providingApplication.getProviderFormat(); break; /* enIx */ } @@ -2863,6 +2948,7 @@ void cprLookupByAttribute { /* We cannot "improve" this result by keep searching the CRR vector, so we return */ *perAttrPa = crr->contextRegistration.providingApplication.get(); + *providerFormatP = crr->contextRegistration.providingApplication.getProviderFormat(); return; } diff --git a/src/lib/mongoBackend/MongoGlobal.h b/src/lib/mongoBackend/MongoGlobal.h index 5080d9fba4..a6ad98e581 100644 --- a/src/lib/mongoBackend/MongoGlobal.h +++ b/src/lib/mongoBackend/MongoGlobal.h @@ -505,7 +505,8 @@ extern void cprLookupByAttribute std::string* perEntPa, MimeType* perEntPaMimeType, std::string* perAttrPa, - MimeType* perAttrPaMimeType + MimeType* perAttrPaMimeType, + ProviderFormat* providerFormatP ); diff --git a/src/lib/ngsi10/UpdateContextRequest.cpp b/src/lib/ngsi10/UpdateContextRequest.cpp index 967692012b..bbffcd2e23 100644 --- a/src/lib/ngsi10/UpdateContextRequest.cpp +++ b/src/lib/ngsi10/UpdateContextRequest.cpp @@ -39,6 +39,7 @@ #include "convenience/UpdateContextAttributeRequest.h" + /* **************************************************************************** * * UpdateContextRequest::UpdateContextRequest - @@ -53,13 +54,15 @@ UpdateContextRequest::UpdateContextRequest() * * UpdateContextRequest::UpdateContextRequest - */ -UpdateContextRequest::UpdateContextRequest(const std::string& _contextProvider, Entity* eP) +UpdateContextRequest::UpdateContextRequest(const std::string& _contextProvider, ProviderFormat _providerFormat, Entity* eP) { contextProvider = _contextProvider; + providerFormat = _providerFormat; entityVector.push_back(new Entity(eP->id, eP->type, eP->isPattern)); } + /* **************************************************************************** * * UpdateContextRequest::toJsonV1 - @@ -68,9 +71,10 @@ std::string UpdateContextRequest::toJsonV1(bool asJsonObject) { std::string out = ""; - // JSON commas: - // Both fields are MANDATORY, so, comma after "contextElementVector" - // + // + // About JSON commas: + // Both fields are MANDATORY, so, always comma after "entityVector" + // out += startTag(); out += entityVector.toJsonV1(asJsonObject, UpdateContext, true); out += valueTag("updateAction", actionTypeString(V1, updateActionType), false); @@ -80,6 +84,7 @@ std::string UpdateContextRequest::toJsonV1(bool asJsonObject) } + /* **************************************************************************** * * UpdateContextRequest::check - diff --git a/src/lib/ngsi10/UpdateContextRequest.h b/src/lib/ngsi10/UpdateContextRequest.h index e68838bf74..e8b39424a9 100644 --- a/src/lib/ngsi10/UpdateContextRequest.h +++ b/src/lib/ngsi10/UpdateContextRequest.h @@ -55,9 +55,10 @@ typedef struct UpdateContextRequest ActionType updateActionType; // Mandatory std::string contextProvider; // Not part of the payload - used internally only + ProviderFormat providerFormat; // Not part of the payload - used internally only UpdateContextRequest(); - UpdateContextRequest(const std::string& _contextProvider, Entity* eP); + UpdateContextRequest(const std::string& _contextProvider, ProviderFormat _providerFormat, Entity* eP); std::string toJsonV1(bool asJsonObject); std::string check(ApiVersion apiVersion, bool asJsonObject, const std::string& predetectedError); diff --git a/src/lib/ngsi10/UpdateContextRequest.h.orig b/src/lib/ngsi10/UpdateContextRequest.h.orig new file mode 100644 index 0000000000..e68838bf74 --- /dev/null +++ b/src/lib/ngsi10/UpdateContextRequest.h.orig @@ -0,0 +1,97 @@ +#ifndef SRC_LIB_NGSI10_UPDATECONTEXTREQUEST_H_ +#define SRC_LIB_NGSI10_UPDATECONTEXTREQUEST_H_ + +/* +* +* Copyright 2013 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 +* +* Author: Ken Zangelin +*/ +#include +#include + +#include "apiTypesV2/EntityVector.h" +#include "orionTypes/UpdateActionType.h" +#include "apiTypesV2/Entity.h" +#include "apiTypesV2/Entities.h" + + + +/* **************************************************************************** +* +* Forward declarations +*/ +struct UpdateContextElementRequest; +struct AppendContextElementRequest; +struct UpdateContextAttributeRequest; + + + +/* **************************************************************************** +* +* UpdateContextRequest - +*/ +typedef struct UpdateContextRequest +{ + EntityVector entityVector; // Mandatory + ActionType updateActionType; // Mandatory + + std::string contextProvider; // Not part of the payload - used internally only + + UpdateContextRequest(); + UpdateContextRequest(const std::string& _contextProvider, Entity* eP); + + std::string toJsonV1(bool asJsonObject); + std::string check(ApiVersion apiVersion, bool asJsonObject, const std::string& predetectedError); + void release(void); + ContextAttribute* attributeLookup(Entity* eP, const std::string& attributeName); + + + void fill(const UpdateContextElementRequest* ucerP, + const std::string& entityId, + const std::string& entityType); + + void fill(const AppendContextElementRequest* acerP, + const std::string& entityId, + const std::string& entityType); + + void fill(const std::string& entityId, + const std::string& entityType, + const std::string& isPattern, + const std::string& attributeName, + ActionType _updateActionType); + + void fill(const UpdateContextAttributeRequest* ucarP, + const std::string& entityId, + const std::string& entityType, + const std::string& attributeName, + ActionType _updateActionType); + + void fill(const Entity* entP, ActionType _updateActionType); + void fill(const std::string& entityId, + ContextAttribute* attributeP, + ActionType _updateActionType, + const std::string& type = ""); + + void fill(Entities* entities, ActionType _updateActionType); +} UpdateContextRequest; + +#endif // SRC_LIB_NGSI10_UPDATECONTEXTREQUEST_H_ diff --git a/src/lib/serviceRoutines/postUpdateContext.cpp b/src/lib/serviceRoutines/postUpdateContext.cpp index 114a56092f..93fc90baed 100644 --- a/src/lib/serviceRoutines/postUpdateContext.cpp +++ b/src/lib/serviceRoutines/postUpdateContext.cpp @@ -101,7 +101,7 @@ static bool forwardsPending(UpdateContextResponse* upcrsP) * 7. Freeing memory * */ -static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, UpdateContextResponse* upcrsP) +static bool updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, UpdateContextResponse* upcrsP) { std::string ip; std::string protocol; @@ -124,9 +124,10 @@ static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, Upda // SccBadRequest should have been returned before, when it was registered! // upcrsP->errorCode.fill(SccContextElementNotFound, ""); - return; + return false; } + LM_T(LmtForward, ("*** Provider Format: %d", upcrP->providerFormat)); // // 2. Render the string of the request we want to forward @@ -137,13 +138,39 @@ static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, Upda ciP->outMimeType = JSON; - // - // FIXME: Forwards are done using NGSIv1 only, for now - // This will hopefully change soon ... - // Once we implement forwards in NGSIv2, this toJsonV1() should be like this: - // TIMED_RENDER(payload = upcrP->toJson()); - // - TIMED_RENDER(payload = upcrP->toJsonV1(asJsonObject)); + std::string verb; + std::string resource; + std::string tenant = ciP->tenant; + std::string servicePath = (ciP->httpHeaders.servicePathReceived == true)? ciP->httpHeaders.servicePath : ""; + std::string mimeType = "application/json"; + std::string out; + int r; + + if (upcrP->providerFormat == PfJson) + { + TIMED_RENDER(payload = upcrP->toJsonV1(asJsonObject)); + + verb = "POST"; + resource = prefix + "/updateContext"; + } + else + { + std::vector nullFilter; + Entity* eP = upcrP->entityVector[0]; + + eP->renderId = false; + + TIMED_RENDER(payload = eP->toJson(NGSI_V2_NORMALIZED, nullFilter, false, nullFilter)); + + resource = prefix + "/entities/" + eP->id + "/attrs"; + verb = "PATCH"; + + if (eP->type != "") + { + // Add ?type= to 'resource' + resource += "?type=" + eP->type; + } + } ciP->outMimeType = outMimeType; cleanPayload = (char*) payload.c_str(); @@ -152,14 +179,6 @@ static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, Upda // 3. Send the request to the Context Provider (and await the reply) // FIXME P7: Should Rush be used? // - std::string verb = "POST"; - std::string resource = prefix + "/updateContext"; - std::string tenant = ciP->tenant; - std::string servicePath = (ciP->httpHeaders.servicePathReceived == true)? ciP->httpHeaders.servicePath : ""; - std::string mimeType = "application/json"; - std::string out; - int r; - LM_T(LmtCPrForwardRequestPayload, ("forward updateContext request payload: %s", payload.c_str())); std::map noHeaders; @@ -188,82 +207,116 @@ static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, Upda { upcrsP->errorCode.fill(SccContextElementNotFound, "error forwarding update"); LM_E(("Runtime Error (error '%s' forwarding 'Update' to providing application)", out.c_str())); - return; + return false; } LM_T(LmtCPrForwardResponsePayload, ("forward updateContext response payload: %s", out.c_str())); - // - // 4. Parse the response and fill in a binary UpdateContextResponse + // If NGSIv1 (providerFormat == PfJson): + // 4. Parse the response and fill in a binary UpdateContextResponse + // 5. Fill in the response from the redirection into the response of this function + // 6. 'Fix' StatusCode + // 7. Free up memory + // + // If NGSIv2: + // 4. Look for "204 No Content" in the response of the forwarded request + // 5. If found: OK, else, error // - std::string s; - std::string errorMsg; + if (upcrP->providerFormat == PfJson) + { + LM_T(LmtForward, ("upcrP->providerFormat == PfJson")); + // + // 4. Parse the response and fill in a binary UpdateContextResponse + // + std::string s; + std::string errorMsg; - cleanPayload = jsonPayloadClean(out.c_str()); + cleanPayload = jsonPayloadClean(out.c_str()); + + if ((cleanPayload == NULL) || (cleanPayload[0] == 0)) + { + // + // This is really an internal error in the Context Provider + // It is not in the orion broker though, so 404 is returned + // + LM_W(("Other Error (context provider response to UpdateContext is empty)")); + upcrsP->errorCode.fill(SccContextElementNotFound, "invalid context provider response"); + return false; + } - if ((cleanPayload == NULL) || (cleanPayload[0] == 0)) - { // - // This is really an internal error in the Context Provider - // It is not in the orion broker though, so 404 is returned + // NOTE + // When coming from a convenience operation, such as GET /v1/contextEntities/EID/attributes/attrName, + // the verb/method in ciP is GET. However, the parsing function expects a POST, as if it came from a + // POST /v1/updateContext. + // So, here we change the verb/method for POST. // - LM_W(("Other Error (context provider response to UpdateContext is empty)")); - upcrsP->errorCode.fill(SccContextElementNotFound, "invalid context provider response"); - return; - } + ParseData parseData; - // - // NOTE - // When coming from a convenience operation, such as GET /v1/contextEntities/EID/attributes/attrName, - // the verb/method in ciP is GET. However, the parsing function expects a POST, as if it came from a - // POST /v1/updateContext. - // So, here we change the verb/method for POST. - // - ParseData parseData; + ciP->verb = POST; + ciP->method = "POST"; - ciP->verb = POST; - ciP->method = "POST"; + parseData.upcrs.res.errorCode.fill(SccOk); - parseData.upcrs.res.errorCode.fill(SccOk); + LM_T(LmtForward, ("Parsing Response of Forwarded Request: '%s'", cleanPayload)); + s = jsonTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, NULL); + LM_T(LmtForward, ("Parse Result: %s", s.c_str())); - s = jsonTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, NULL); + if (s != "OK") + { + LM_W(("Internal Error (error parsing reply from prov app: %s)", errorMsg.c_str())); + upcrsP->errorCode.fill(SccContextElementNotFound, ""); + parseData.upcr.res.release(); + parseData.upcrs.res.release(); + return false; + } - if (s != "OK") - { - LM_W(("Internal Error (error parsing reply from prov app: %s)", errorMsg.c_str())); - upcrsP->errorCode.fill(SccContextElementNotFound, ""); - parseData.upcr.res.release(); - parseData.upcrs.res.release(); - return; - } + // + // 5. Fill in the response from the redirection into the response of this function + // + upcrsP->fill(&parseData.upcrs.res); - // - // 5. Fill in the response from the redirection into the response of this function - // - upcrsP->fill(&parseData.upcrs.res); + // + // 6. 'Fix' StatusCode + // + if (upcrsP->errorCode.code == SccNone) + { + upcrsP->errorCode.fill(SccOk); + } - // - // 6. 'Fix' StatusCode - // - if (upcrsP->errorCode.code == SccNone) - { - upcrsP->errorCode.fill(SccOk); - } + if ((upcrsP->contextElementResponseVector.size() == 1) && (upcrsP->contextElementResponseVector[0]->statusCode.code == SccContextElementNotFound)) + { + upcrsP->errorCode.fill(SccContextElementNotFound); + } - if ((upcrsP->contextElementResponseVector.size() == 1) && (upcrsP->contextElementResponseVector[0]->statusCode.code == SccContextElementNotFound)) - { - upcrsP->errorCode.fill(SccContextElementNotFound); + // + // 7. Free up memory + // + parseData.upcr.res.release(); + parseData.upcrs.res.release(); + + return true; } + else // NGSIv2 + { + LM_T(LmtForward, ("upcrP->providerFormat == V2. out: '%s'", out.c_str())); + // NGSIv2 forward - no payload to be received, just a 204 No Content HTTP Header + if (strstr(out.c_str(), "204 No Content") != NULL) + { + LM_T(LmtForward, ("Found '204 No Content'")); + upcrsP->errorCode.fill(SccNone); + return true; + } + LM_W(("Other Error (unexpected response from context provider: %s)", out.c_str())); + upcrsP->errorCode.fill(SccReceiverInternalError); + return false; + } - // - // 7. Freeing memory - // - parseData.upcr.res.release(); - parseData.upcrs.res.release(); + // Can't reach this point - no return-statement needed } @@ -525,6 +578,7 @@ std::string postUpdateContext // If there is nothing to forward, just return the result // bool forwarding = forwardsPending(upcrsP); + LM_T(LmtForward, ("forwardsPending returned %s", FT(forwarding))); if (forwarding == false) { TIMED_RENDER(answer = upcrsP->toJsonV1(asJsonObject)); @@ -623,7 +677,7 @@ std::string postUpdateContext UpdateContextRequest* reqP = requestV.lookup(aP->providingApplication.get()); if (reqP == NULL) { - reqP = new UpdateContextRequest(aP->providingApplication.get(), &cerP->entity); + reqP = new UpdateContextRequest(aP->providingApplication.get(), aP->providingApplication.providerFormat, &cerP->entity); reqP->updateActionType = ActionTypeUpdate; requestV.push_back(reqP); } @@ -659,6 +713,7 @@ std::string postUpdateContext // Calling each of the Context Providers, merging their results into the // total response 'response' // + bool forwardOk = true; for (unsigned int ix = 0; ix < requestV.size() && ix < cprForwardLimit; ++ix) { @@ -669,8 +724,14 @@ std::string postUpdateContext } UpdateContextResponse upcrs; + bool b; - updateForward(ciP, requestV[ix], &upcrs); + b = updateForward(ciP, requestV[ix], &upcrs); + + if (b == false) + { + forwardOk = false; + } // // Add the result from the forwarded update to the total response in 'response' @@ -678,26 +739,30 @@ std::string postUpdateContext response.merge(&upcrs); } + // // Note this is a slight break in the separation of concerns among the different layers (i.e. // serviceRoutine/ logic should work in a "NGSIv1 isolated context"). However, it seems to be // a smart way of dealing with partial update situations + // if (ciP->apiVersion == V2) { + LM_T(LmtForward, ("ciP->apiVersion == V2")); + // // Adjust OrionError response in the case of partial updates. This may happen in CPr forwarding // scenarios. Note that mongoBackend logic "splits" successfull updates and failing updates in // two different CER (maybe using the same entity) - + // std::string failing = ""; - unsigned int fails = 0; + unsigned int failures = 0; + LM_T(LmtForward, ("Going over a contextElementResponseVector of %d items", response.contextElementResponseVector.size())); for (unsigned int ix = 0; ix < response.contextElementResponseVector.size(); ++ix) { - ContextElementResponse* cerP = response.contextElementResponseVector[ix]; if (cerP->statusCode.code != SccOk) { - fails++; + failures++; std::string failingPerCer = ""; for (unsigned int jx = 0; jx < cerP->entity.attributeVector.size(); ++jx) @@ -713,29 +778,35 @@ std::string postUpdateContext } } + // // Note that we modify parseDataP->upcrs.res.oe and not response.oe, as the former is the // one used by the calling postBatchUpdate() function at serviceRoutineV2 library - if (fails == response.contextElementResponseVector.size()) + // + if ((forwardOk == true) && (failures == 0)) + { + parseDataP->upcrs.res.oe.fill(SccNone, ""); + } + else if (failures == response.contextElementResponseVector.size()) { parseDataP->upcrs.res.oe.fill(SccContextElementNotFound, ERROR_DESC_NOT_FOUND_ENTITY, ERROR_NOT_FOUND); } - else if (fails > 0) + else if (failures > 0) { // Removing trailing ", " failing = failing.substr(0, failing.size() - 2); - // If some CER (but not all) fails, then it is a partial update + // If some CER (but not all) fail, then it is a partial update parseDataP->upcrs.res.oe.fill(SccContextElementNotFound, "Attributes that were not updated: { " + failing + " }", "PartialUpdate"); } - else // fails == 0 + else // failures == 0 { // No failure, so invalidate any possible OrionError filled by mongoBackend on the mongoUpdateContext step parseDataP->upcrs.res.oe.fill(SccNone, ""); } - } else // v1 { + LM_T(LmtForward, ("ciP->apiVersion != V2")); // Note that v2 case doesn't use an actual response (so no need to waste time rendering it). // We render in the v1 case only TIMED_RENDER(answer = response.toJsonV1(asJsonObject)); diff --git a/test/functionalTest/cases/3068_ngsi_v2_based_forwarding/forwards_with_accumulator.test b/test/functionalTest/cases/3068_ngsi_v2_based_forwarding/forwards_with_accumulator.test index 2d6f1a4ed8..0ea7a99e95 100644 --- a/test/functionalTest/cases/3068_ngsi_v2_based_forwarding/forwards_with_accumulator.test +++ b/test/functionalTest/cases/3068_ngsi_v2_based_forwarding/forwards_with_accumulator.test @@ -425,5 +425,6 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}) --TEARDOWN-- +accumulatorStop brokerStop CB dbDrop CB diff --git a/test/functionalTest/harnessFunctions.sh b/test/functionalTest/harnessFunctions.sh index 273851d57d..8541346964 100644 --- a/test/functionalTest/harnessFunctions.sh +++ b/test/functionalTest/harnessFunctions.sh @@ -715,6 +715,14 @@ function accumulatorStart() shift fi + url="/notify" + if [ "$1" = "--url" ] + then + url="$2" + shift + shift + fi + bindIp=$1 port=$2 @@ -731,7 +739,7 @@ function accumulatorStart() accumulatorStop $port - accumulator-server.py --port $port --url /notify --host $bindIp $pretty $https $key $cert > /tmp/accumulator_${port}_stdout 2> /tmp/accumulator_${port}_stderr & + accumulator-server.py --port $port --url $url --host $bindIp $pretty $https $key $cert > /tmp/accumulator_${port}_stdout 2> /tmp/accumulator_${port}_stderr & echo accumulator running as PID $$ # Wait until accumulator has started or we have waited a given maximum time