/* **************************************************************************** * * servicePathSplit - */ int servicePathSplit(ConnectionInfo* ciP) { #if 0 // // Special case: empty service-path // // FIXME P4: We're not sure what this 'fix' really fixes. // Must implement a functest to reproduce this situation. // And, if that is not possible, just remove the whole thing // if ((ciP->httpHeaders.servicePathReceived == true) && (ciP->servicePath == "")) { OrionError e(SccBadRequest, "empty service path"); ciP->answer = e.render(ciP, ""); alarmMgr.badInput(clientIp, "empty service path"); return -1; } #endif int servicePaths = stringSplit(ciP->servicePath, ',', ciP->servicePathV); if (servicePaths == 0) { /* In this case the result is a vector with an empty string */ ciP->servicePathV.push_back(""); return 0; } if (servicePaths > SERVICE_PATH_MAX_COMPONENTS) { OrionError e(SccBadRequest, "too many service paths - a maximum of ten service paths is allowed"); ciP->answer = e.render(ciP, ""); return -1; } for (int ix = 0; ix < servicePaths; ++ix) { std::string stripped = std::string(wsStrip((char*) ciP->servicePathV[ix].c_str())); ciP->servicePathV[ix] = removeTrailingSlash(stripped); // This was previously a LM_T trace, but we have "promoted" it to INFO due to it is needed to check logs in a .test case (case 0392 service_path_http_header.test) LM_I(("Service Path %d: '%s'", ix, ciP->servicePathV[ix].c_str())); } for (int ix = 0; ix < servicePaths; ++ix) { int s; if ((s = servicePathCheck(ciP, ciP->servicePathV[ix].c_str())) != 0) { return s; } } return 0; }
/* **************************************************************************** * * RegistrationId::render - */ std::string RegistrationId::render(RequestType requestType, Format format, const std::string& indent, bool comma) { if (string == "") { if (requestType == RegisterResponse) // registrationId is MANDATORY for RegisterContextResponse { string = "000000000000000000000000"; LM_I(("No registrationId - setting the registrationId to 24 zeroes")); } else { return ""; } } return valueTag(indent, "registrationId", string, format, comma); }
/* **************************************************************************** * * countEntities - * */ static long long countEntities(const std::string& tenant, const std::vector<std::string>& servicePathV,std::string entityType) { DBClientBase* connection = NULL; std::string idType = std::string("_id.") + ENT_ENTITY_TYPE; std::string idServicePath = std::string("_id.") + ENT_SERVICE_PATH; BSONObj query = BSON(idType << entityType << idServicePath << fillQueryServicePath(servicePathV)); LM_T(LmtMongo, ("count() in '%s' collection: '%s'", getEntitiesCollectionName(tenant).c_str(), query.toString().c_str())); try { connection = getMongoConnection(); long long c = connection->count(getEntitiesCollectionName(tenant).c_str(), query); releaseMongoConnection(connection); LM_I(("Database Operation Successful (%s)", query.toString().c_str())); return c; } catch (const DBException& e) { releaseMongoConnection(connection); LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), e.what())); } catch (...) { releaseMongoConnection(connection); LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), "generic exception")); } return -1; }
/* **************************************************************************** * * mongoUnsubscribeContext - */ HttpStatusCode mongoUnsubscribeContext(UnsubscribeContextRequest* requestP, UnsubscribeContextResponse* responseP, const std::string& tenant) { bool reqSemTaken; BSONObj sub; DBClientBase* connection = NULL; reqSemTake(__FUNCTION__, "ngsi10 unsubscribe request", SemWriteOp, &reqSemTaken); LM_T(LmtMongo, ("Unsubscribe Context")); /* No matter if success or failure, the subscriptionId in the response is always the one * in the request */ responseP->subscriptionId = requestP->subscriptionId; if (responseP->subscriptionId.get() == "") { responseP->statusCode.fill(SccContextElementNotFound); LM_W(("Bad Input (no subscriptionId)")); return SccOk; } LM_T(LmtMongo, ("findOne() in '%s' collection _id '%s'}", getSubscribeContextCollectionName(tenant).c_str(), requestP->subscriptionId.get().c_str())); /* Look for document */ connection = getMongoConnection(); try { OID id = OID(requestP->subscriptionId.get()); sub = connection->findOne(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << id)); releaseMongoConnection(connection); LM_I(("Database Operation Successful (findOne _id: %s)", id.toString().c_str())); } catch (const AssertionException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo assertion exception)", reqSemTaken); // // This happens when OID format is wrong // FIXME: this checking should be done at parsing stage, without progressing to // mongoBackend. By the moment we can live this here, but we should remove in the future // (old issue #95) // responseP->statusCode.fill(SccContextElementNotFound); LM_W(("Bad Input (invalid OID format)")); return SccOk; } catch (const DBException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } catch (...) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo generic exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } if (sub.isEmpty()) { reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (no subscriptions found)", reqSemTaken); responseP->statusCode.fill(SccContextElementNotFound, std::string("subscriptionId: /") + requestP->subscriptionId.get() + "/"); return SccOk; } /* Remove document in MongoDB */ // FIXME: I will prefer to do the find and remove in a single operation. Is the some similar // to findAndModify for this? LM_T(LmtMongo, ("remove() in '%s' collection _id '%s'}", getSubscribeContextCollectionName(tenant).c_str(), requestP->subscriptionId.get().c_str())); connection = getMongoConnection(); try { connection->remove(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << OID(requestP->subscriptionId.get()))); releaseMongoConnection(connection); LM_I(("Database Operation Successful (remove _id: %s)", requestP->subscriptionId.get().c_str())); } catch (const DBException &e) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - remove() _id: " + requestP->subscriptionId.get().c_str() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } catch (...) { releaseMongoConnection(connection); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo generic exception)", reqSemTaken); responseP->statusCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - remove() _id: " + requestP->subscriptionId.get().c_str() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->statusCode.details.c_str())); return SccOk; } /* Destroy any previous ONTIMEINTERVAL thread */ getNotifier()->destroyOntimeIntervalThreads(requestP->subscriptionId.get()); responseP->statusCode.fill(SccOk); reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request", reqSemTaken); // // Removing subscription from cache // subCache->remove(tenant, "", requestP->subscriptionId.get()); return SccOk; }
/* **************************************************************************** * * mongoEntityTypes - */ HttpStatusCode mongoEntityTypes ( EntityTypesResponse* responseP, const std::string& tenant, const std::vector<std::string>& servicePathV, std::map<std::string, std::string>& uriParams ) { unsigned int offset = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str()); unsigned int limit = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str()); std::string detailsString = uriParams[URI_PARAM_PAGINATION_DETAILS]; bool details = (strcasecmp("on", detailsString.c_str()) == 0)? true : false; LM_T(LmtMongo, ("Query Entity Types")); LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false")); reqSemTake(__FUNCTION__, "query types request"); DBClientBase* connection = getMongoConnection(); /* Compose query based on this aggregation command: * * FIXME P9: taking into account that type is no longer used as part of the attribute "key", not sure if the * aggregation query below is fully correct * * db.runCommand({aggregate: "entities", * pipeline: [ {$match: { "_id.servicePath": /.../ } }, * {$project: {_id: 1, "attrs.name": 1, "attrs.type": 1} }, * {$project: { "attrs" * {$cond: [ {$eq: [ "$attrs", [ ] ] }, [null], "$attrs"] } * } * }, * {$unwind: "$attrs"}, * {$group: {_id: "$_id.type", attrs: {$addToSet: "$attrs"}} }, * {$sort: {_id: 1} } * ] * }) * * The $cond part is hard... more information at http://stackoverflow.com/questions/27510143/empty-array-prevents-document-to-appear-in-query * As a consequence, some "null" values may appear in the resulting attrs vector, which are prunned by the result processing logic. * * FIXME P6: in the future, we can interpret the collapse parameter at this layer. If collapse=true so we don't need attributes, the * following command can be used: * * db.runCommand({aggregate: "entities", pipeline: [ {$group: {_id: "$_id.type"} }]}) * */ BSONObj result; // Building the projection part of the query that includes types that have no attributes // See bug: https://github.com/telefonicaid/fiware-orion/issues/686 BSONArrayBuilder emptyArrayBuilder; BSONArrayBuilder nulledArrayBuilder; nulledArrayBuilder.appendNull(); // We are using the $cond: [ .. ] and not the $cond: { .. } one, as the former is the only one valid in MongoDB 2.4 BSONObj projection = BSON( "$project" << BSON( "attrs" << BSON( "$cond" << BSON_ARRAY( BSON("$eq" << BSON_ARRAY(S_ATTRS << emptyArrayBuilder.arr()) ) << nulledArrayBuilder.arr() << S_ATTRS ) ) ) ); BSONObj cmd = BSON("aggregate" << COL_ENTITIES << "pipeline" << BSON_ARRAY( BSON("$match" << BSON(C_ID_SERVICEPATH << fillQueryServicePath(servicePathV))) << BSON("$project" << BSON("_id" << 1 << C_ATTR_NAME << 1 << C_ATTR_TYPE << 1)) << projection << BSON("$unwind" << S_ATTRS) << BSON("$group" << BSON("_id" << CS_ID_ENTITY << "attrs" << BSON("$addToSet" << S_ATTRS))) << BSON("$sort" << BSON("_id" << 1)) ) ); LM_T(LmtMongo, ("runCommand() in '%s' database: '%s'", composeDatabaseName(tenant).c_str(), cmd.toString().c_str())); mongoSemTake(__FUNCTION__, "aggregation command"); try { connection->runCommand(composeDatabaseName(tenant).c_str(), cmd, result); mongoSemGive(__FUNCTION__, "aggregation command"); LM_I(("Database Operation Successful (%s)", cmd.toString().c_str())); } catch (const DBException& e) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + e.what(); LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } catch (...) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + "generic"; LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } /* Processing result to build response */ LM_T(LmtMongo, ("aggregation result: %s", result.toString().c_str())); std::vector<BSONElement> resultsArray = result.getField("result").Array(); /* Another strategy to implement pagination is to use the $skip and $limit operators in the * aggregation framework. However, doing so, we don't know the total number of results, which can * be needed in the case of details=on (using that approach, we need to do two queries: one to get * the count and other to get the actual results with $skip and $limit, in the same "transaction" to * avoid incoherence between both if some entity type is created or deleted in the process). * * However, considering that the number of types will be small compared with the number of entities, * the current approach seems to be ok */ for (unsigned int ix = offset; ix < MIN(resultsArray.size(), offset + limit); ++ix) { BSONObj resultItem = resultsArray[ix].embeddedObject(); TypeEntity* type = new TypeEntity(resultItem.getStringField("_id")); std::vector<BSONElement> attrsArray = resultItem.getField("attrs").Array(); if (!attrsArray[0].isNull()) { for (unsigned int jx = 0; jx < attrsArray.size(); ++jx) { /* This is the place in which null elements in the resulting attrs vector are prunned */ if (attrsArray[jx].isNull()) { continue; } BSONObj jAttr = attrsArray[jx].embeddedObject(); ContextAttribute* ca = new ContextAttribute(jAttr.getStringField(ENT_ATTRS_NAME), jAttr.getStringField(ENT_ATTRS_TYPE)); type->contextAttributeVector.push_back(ca); } } responseP->typeEntityVector.push_back(type); } char detailsMsg[256]; if (responseP->typeEntityVector.size() > 0) { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Count: %d", (int) resultsArray.size()); responseP->statusCode.fill(SccOk, detailsMsg); } else { responseP->statusCode.fill(SccOk); } } else { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Number of types: %d. Offset is %d", (int) resultsArray.size(), offset); responseP->statusCode.fill(SccContextElementNotFound, detailsMsg); } else { responseP->statusCode.fill(SccContextElementNotFound); } } reqSemGive(__FUNCTION__, "query types request"); return SccOk; }
/* **************************************************************************** * * mongoAttributesForEntityType - */ HttpStatusCode mongoAttributesForEntityType ( std::string entityType, EntityTypeAttributesResponse* responseP, const std::string& tenant, const std::vector<std::string>& servicePathV, std::map<std::string, std::string>& uriParams ) { unsigned int offset = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str()); unsigned int limit = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str()); std::string detailsString = uriParams[URI_PARAM_PAGINATION_DETAILS]; bool details = (strcasecmp("on", detailsString.c_str()) == 0)? true : false; // Setting the name of the entity type for the response responseP->entityType.type = entityType; LM_T(LmtMongo, ("Query Types Attribute for <%s>", entityType.c_str())); LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false")); reqSemTake(__FUNCTION__, "query types attributes request"); DBClientBase* connection = getMongoConnection(); /* Compose query based on this aggregation command: * * FIXME P9: taking into account that type is no longer used as part of the attribute "key", not sure if the * aggregation query below is fully correct * * db.runCommand({aggregate: "entities", * pipeline: [ {$match: { "_id.type": "TYPE" , "_id.servicePath": /.../ } }, * {$project: {_id: 1, "attrs.name": 1, "attrs.type": 1} }, * {$unwind: "$attrs"}, * {$group: {_id: "$_id.type", attrs: {$addToSet: "$attrs"}} }, * {$unwind: "$attrs"}, * {$group: {_id: "$attrs" }}, * {$sort: {_id.name: 1, _id.type: 1} } * ] * }) * */ BSONObj result; BSONObj cmd = BSON("aggregate" << COL_ENTITIES << "pipeline" << BSON_ARRAY( BSON("$match" << BSON(C_ID_ENTITY << entityType << C_ID_SERVICEPATH << fillQueryServicePath(servicePathV))) << BSON("$project" << BSON("_id" << 1 << C_ATTR_NAME << 1 << C_ATTR_TYPE << 1)) << BSON("$unwind" << S_ATTRS) << BSON("$group" << BSON("_id" << CS_ID_ENTITY << "attrs" << BSON("$addToSet" << S_ATTRS))) << BSON("$unwind" << S_ATTRS) << BSON("$group" << BSON("_id" << S_ATTRS)) << BSON("$sort" << BSON(C_ID_NAME << 1 << C_ID_TYPE << 1)) ) ); LM_T(LmtMongo, ("runCommand() in '%s' database: '%s'", composeDatabaseName(tenant).c_str(), cmd.toString().c_str())); mongoSemTake(__FUNCTION__, "aggregation command"); try { connection->runCommand(composeDatabaseName(tenant).c_str(), cmd, result); mongoSemGive(__FUNCTION__, "aggregation command"); LM_I(("Database Operation Successful (%s)", cmd.toString().c_str())); } catch (const DBException& e) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + e.what(); LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } catch (...) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + "generic"; LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } /* Processing result to build response*/ LM_T(LmtMongo, ("aggregation result: %s", result.toString().c_str())); std::vector<BSONElement> resultsArray = result.getField("result").Array(); /* See comment above in the other method regarding this strategy to implement pagination */ for (unsigned int ix = offset; ix < MIN(resultsArray.size(), offset + limit); ++ix) { BSONElement idField = resultsArray[ix].embeddedObject().getField("_id"); // // BSONElement::eoo returns true if 'not found', i.e. the field "_id" doesn't exist in 'sub' // // Now, if 'resultsArray[ix].embeddedObject().getField("_id")' is not found, if we continue, // calling embeddedObject() on it, then we get an exception and the broker crashes. // if (idField.eoo() == true) { LM_E(("Database Error (error retrieving _id field in doc: %s)", resultsArray[ix].embeddedObject().toString().c_str())); continue; } BSONObj resultItem = idField.embeddedObject(); ContextAttribute* ca = new ContextAttribute(resultItem.getStringField(ENT_ATTRS_NAME), resultItem.getStringField(ENT_ATTRS_TYPE)); responseP->entityType.contextAttributeVector.push_back(ca); } char detailsMsg[256]; if (responseP->entityType.contextAttributeVector.size() > 0) { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Count: %d", (int) resultsArray.size()); responseP->statusCode.fill(SccOk, detailsMsg); } else { responseP->statusCode.fill(SccOk); } } else { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Number of attributes: %d. Offset is %d", (int) resultsArray.size(), offset); responseP->statusCode.fill(SccContextElementNotFound, detailsMsg); } else { responseP->statusCode.fill(SccContextElementNotFound); } } reqSemGive(__FUNCTION__, "query types request"); return SccOk; }
/* **************************************************************************** * * mongoUpdateContextSubscription - */ HttpStatusCode mongoUpdateContextSubscription(UpdateContextSubscriptionRequest* requestP, UpdateContextSubscriptionResponse* responseP, Format inFormat, const std::string& tenant) { reqSemTake(__FUNCTION__, "ngsi10 update subscription request"); LM_T(LmtMongo, ("Update Context Subscription")); DBClientBase* connection = getMongoConnection(); /* Look for document */ BSONObj sub; try { OID id = OID(requestP->subscriptionId.get()); mongoSemTake(__FUNCTION__, "findOne in SubscribeContextCollection"); sub = connection->findOne(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << id)); mongoSemGive(__FUNCTION__, "findOne in SubscribeContextCollection"); LM_I(("Database Operation Successful (findOne _id: %s)", id.toString().c_str())); } catch (const AssertionException &e) { /* This happens when OID format is wrong */ // FIXME P4: this checking should be done at the parsing stage, without progressing to // mongoBackend. For the moment we can leave this here, but we should remove it in the future // (old issue #95) mongoSemGive(__FUNCTION__, "findOne in SubscribeContextCollection (mongo assertion exception)"); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (mongo assertion exception)"); responseP->subscribeError.errorCode.fill(SccContextElementNotFound); LM_W(("Bad Input (invalid OID format)")); return SccOk; } catch (const DBException &e) { mongoSemGive(__FUNCTION__, "findOne in SubscribeContextCollection (mongo db exception)"); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (mongo db exception)"); responseP->subscribeError.errorCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->subscribeError.errorCode.details.c_str())); return SccOk; } catch (...) { mongoSemGive(__FUNCTION__, "findOne in SubscribeContextCollection (mongo generic exception)"); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (mongo generic exception)"); responseP->subscribeError.errorCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - findOne() _id: " + requestP->subscriptionId.get() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->subscribeError.errorCode.details.c_str())); return SccOk; } if (sub.isEmpty()) { responseP->subscribeError.errorCode.fill(SccContextElementNotFound); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (no subscriptions found)"); return SccOk; } /* We start with an empty BSONObjBuilder and process requestP for all the fields that can * be updated. I don't like too much this strategy (I would have preferred to start with * a copy of the original document, then modify as neded, but this doesn't seem to be easy * using the API provide by the Mongo C++ driver) * * FIXME: a better implementation strategy could be doing an findAndModify() query to do the * update, so detecting if the document was not found, instead of using findOne() + update() * with $set operation. One operations to MongoDb. vs two operations. */ BSONObjBuilder newSub; /* Entities, attribute list and reference are not updatable, so they are appended directly */ newSub.appendArray(CSUB_ENTITIES, sub.getField(CSUB_ENTITIES).Obj()); newSub.appendArray(CSUB_ATTRS, sub.getField(CSUB_ATTRS).Obj()); newSub.append(CSUB_REFERENCE, STR_FIELD(sub, CSUB_REFERENCE)); /* Duration update */ if (requestP->duration.isEmpty()) { newSub.append(CSUB_EXPIRATION, sub.getField(CSUB_EXPIRATION).numberLong()); } else { long long expiration = getCurrentTime() + requestP->duration.parse(); newSub.append(CSUB_EXPIRATION, expiration); LM_T(LmtMongo, ("New subscription expiration: %l", expiration)); } /* Restriction update */ // FIXME: Restrictions not implemented yet /* Throttling update */ if (!requestP->throttling.isEmpty()) { /* Throttling equal to 0 removes throttling */ long long throttling = requestP->throttling.parse(); if (throttling != 0) { newSub.append(CSUB_THROTTLING, throttling); } } else { /* The hasField check is needed due to Throttling could not be present in the original doc */ if (sub.hasField(CSUB_THROTTLING)) { newSub.append(CSUB_THROTTLING, sub.getField(CSUB_THROTTLING).numberLong()); } } /* Notify conditions */ bool notificationDone = false; if (requestP->notifyConditionVector.size() == 0) { newSub.appendArray(CSUB_CONDITIONS, sub.getField(CSUB_CONDITIONS).embeddedObject()); } else { /* Destroy any previous ONTIMEINTERVAL thread */ getNotifier()->destroyOntimeIntervalThreads(requestP->subscriptionId.get()); /* Build conditions array (including side-effect notifications and threads creation) * In order to do so, we have to create and EntityIdVector and AttributeList from sub * document, given the processConditionVector() signature */ EntityIdVector enV = subToEntityIdVector(sub); AttributeList attrL = subToAttributeList(sub); BSONArray conds = processConditionVector(&requestP->notifyConditionVector, enV, attrL, requestP->subscriptionId.get(), C_STR_FIELD(sub, CSUB_REFERENCE), ¬ificationDone, inFormat, tenant); newSub.appendArray(CSUB_CONDITIONS, conds); /* Remove EntityIdVector and AttributeList dynamic memory */ enV.release(); attrL.release(); } int count = sub.hasField(CSUB_COUNT) ? sub.getIntField(CSUB_COUNT) : 0; /* Last notification */ if (notificationDone) { newSub.append(CSUB_LASTNOTIFICATION, getCurrentTime()); newSub.append(CSUB_COUNT, count + 1); } else { /* The hasField check is needed due to lastNotification/count could not be present in the original doc */ if (sub.hasField(CSUB_LASTNOTIFICATION)) { newSub.append(CSUB_LASTNOTIFICATION, sub.getIntField(CSUB_LASTNOTIFICATION)); } if (sub.hasField(CSUB_COUNT)) { newSub.append(CSUB_COUNT, count); } } /* Adding format to use in notifications */ newSub.append(CSUB_FORMAT, std::string(formatToString(inFormat))); /* Update document in MongoDB */ BSONObj update = newSub.obj(); try { LM_T(LmtMongo, ("update() in '%s' collection _id '%s': %s}", getSubscribeContextCollectionName(tenant).c_str(), requestP->subscriptionId.get().c_str(), update.toString().c_str())); mongoSemTake(__FUNCTION__, "update in SubscribeContextCollection"); connection->update(getSubscribeContextCollectionName(tenant).c_str(), BSON("_id" << OID(requestP->subscriptionId.get())), update); mongoSemGive(__FUNCTION__, "update in SubscribeContextCollection"); LM_I(("Database Operation Successful (update _id: %s, %s)", requestP->subscriptionId.get().c_str(), update.toString().c_str())); } catch (const DBException &e) { mongoSemGive(__FUNCTION__, "update in SubscribeContextCollection (mongo db exception)"); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (mongo db exception)"); responseP->subscribeError.errorCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - update() _id: " + requestP->subscriptionId.get().c_str() + " - update() doc: " + update.toString() + " - exception: " + e.what()); LM_E(("Database Error (%s)", responseP->subscribeError.errorCode.details.c_str())); return SccOk; } catch (...) { mongoSemGive(__FUNCTION__, "update in SubscribeContextCollection (mongo generic exception)"); reqSemGive(__FUNCTION__, "ngsi10 update subscription request (mongo generic exception)"); responseP->subscribeError.errorCode.fill(SccReceiverInternalError, std::string("collection: ") + getSubscribeContextCollectionName(tenant).c_str() + " - update() _id: " + requestP->subscriptionId.get().c_str() + " - update() doc: " + update.toString() + " - exception: " + "generic"); LM_E(("Database Error (%s)", responseP->subscribeError.errorCode.details.c_str())); return SccOk; } /* Duration and throttling are optional parameters, they are only added in the case they * was used for update */ if (!requestP->duration.isEmpty()) { responseP->subscribeResponse.duration = requestP->duration; } if (!requestP->throttling.isEmpty()) { responseP->subscribeResponse.throttling = requestP->throttling; } responseP->subscribeResponse.subscriptionId = requestP->subscriptionId; reqSemGive(__FUNCTION__, "ngsi10 update subscription request"); return SccOk; }
/* **************************************************************************** * * httpRequestSendWithCurl - * * The waitForResponse arguments specifies if the method has to wait for response * before return. If this argument is false, the return string is "" * * NOTE * We are using a hybrid approach, consisting in a static thread-local buffer of a * small size that copes with most notifications to avoid expensive * calloc/free syscalls if the notification payload is not very large. * * RETURN VALUES * httpRequestSendWithCurl returns 0 on success and a negative number on failure: * -1: Invalid port * -2: Invalid IP * -3: Invalid verb * -4: Invalid resource * -5: No Content-Type BUT content present * -6: Content-Type present but there is no content * -7: Total outgoing message size is too big * -9: Error making HTTP request */ int httpRequestSendWithCurl ( CURL *curl, const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& servicePath, const std::string& xauthToken, const std::string& resource, const std::string& orig_content_type, const std::string& content, const std::string& fiwareCorrelation, const std::string& ngisv2AttrFormat, bool useRush, bool waitForResponse, std::string* outP, const std::string& acceptFormat, long timeoutInMilliseconds ) { char portAsString[STRING_SIZE_FOR_INT]; static unsigned long long callNo = 0; std::string result; std::string ip = _ip; struct curl_slist* headers = NULL; MemoryStruct* httpResponse = NULL; CURLcode res; int outgoingMsgSize = 0; std::string content_type(orig_content_type); ++callNo; // For content-type application/json we add charset=utf-8 if (orig_content_type == "application/json") { content_type += "; charset=utf-8"; } if (timeoutInMilliseconds == -1) { timeoutInMilliseconds = defaultTimeout; } lmTransactionStart("to", ip.c_str(), port, resource.c_str()); // Preconditions check if (port == 0) { LM_E(("Runtime Error (port is ZERO)")); lmTransactionEnd(); *outP = "error"; return -1; } if (ip.empty()) { LM_E(("Runtime Error (ip is empty)")); lmTransactionEnd(); *outP = "error"; return -2; } if (verb.empty()) { LM_E(("Runtime Error (verb is empty)")); lmTransactionEnd(); *outP = "error"; return -3; } if (resource.empty()) { LM_E(("Runtime Error (resource is empty)")); lmTransactionEnd(); *outP = "error"; return -4; } if ((content_type.empty()) && (!content.empty())) { LM_E(("Runtime Error (Content-Type is empty but there is actual content)")); lmTransactionEnd(); *outP = "error"; return -5; } if ((!content_type.empty()) && (content.empty())) { LM_E(("Runtime Error (Content-Type non-empty but there is no content)")); lmTransactionEnd(); *outP = "error"; return -6; } // Allocate to hold HTTP response httpResponse = new MemoryStruct; httpResponse->memory = (char*) malloc(1); // will grow as needed httpResponse->size = 0; // no data at this point // // Rush // Every call to httpRequestSend specifies whether RUSH should be used or not. // But, this depends also on how the broker was started, so here the 'useRush' // parameter is cancelled in case the broker was started without rush. // // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and // after that, the host and port of rush is set as ip/port for the message, that is // now sent to rush instead of to its final destination. // Also, a few HTTP headers for rush must be setup. // if ((rushPort == 0) || (rushHost == "")) { useRush = false; } if (useRush) { char rushHeaderPortAsString[STRING_SIZE_FOR_INT]; uint16_t rushHeaderPort = port; std::string rushHeaderIP = ip; std::string headerRushHttp; ip = rushHost; port = rushPort; snprintf(rushHeaderPortAsString, sizeof(rushHeaderPortAsString), "%d", rushHeaderPort); headerRushHttp = "X-relayer-host: " + rushHeaderIP + ":" + rushHeaderPortAsString; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); if (protocol == "https:") { headerRushHttp = "X-relayer-protocol: https"; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); } } snprintf(portAsString, sizeof(portAsString), "%u", port); // ----- User Agent char cvBuf[CURL_VERSION_MAX_LENGTH]; char headerUserAgent[HTTP_HEADER_USER_AGENT_MAX_LENGTH]; snprintf(headerUserAgent, sizeof(headerUserAgent), "User-Agent: orion/%s libcurl/%s", versionGet(), curlVersionGet(cvBuf, sizeof(cvBuf))); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerUserAgent)); headers = curl_slist_append(headers, headerUserAgent); outgoingMsgSize += strlen(headerUserAgent) + 1; // ----- Host char headerHost[HTTP_HEADER_HOST_MAX_LENGTH]; snprintf(headerHost, sizeof(headerHost), "Host: %s:%d", ip.c_str(), (int) port); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerHost)); headers = curl_slist_append(headers, headerHost); outgoingMsgSize += strlen(headerHost) + 1; // ----- Tenant if (tenant != "") { headers = curl_slist_append(headers, ("fiware-service: " + tenant).c_str()); outgoingMsgSize += tenant.size() + 16; // "fiware-service: " } // ----- Service-Path if (servicePath != "") { headers = curl_slist_append(headers, ("Fiware-ServicePath: " + servicePath).c_str()); outgoingMsgSize += servicePath.size() + strlen("Fiware-ServicePath: "); } // ----- X-Auth-Token if (xauthToken != "") { headers = curl_slist_append(headers, ("X-Auth-Token: " + xauthToken).c_str()); outgoingMsgSize += xauthToken.size() + strlen("X-Auth-Token: "); } // ----- Accept std::string acceptedFormats = "application/json"; if (acceptFormat != "") { acceptedFormats = acceptFormat; } std::string acceptString = "Accept: " + acceptedFormats; headers = curl_slist_append(headers, acceptString.c_str()); outgoingMsgSize += acceptString.size(); // ----- Expect headers = curl_slist_append(headers, "Expect: "); outgoingMsgSize += 8; // from "Expect: " // ----- Content-length std::stringstream contentLengthStringStream; contentLengthStringStream << content.size(); std::string headerContentLength = "Content-length: " + contentLengthStringStream.str(); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerContentLength.c_str())); headers = curl_slist_append(headers, headerContentLength.c_str()); outgoingMsgSize += contentLengthStringStream.str().size() + 16; // from "Content-length: " outgoingMsgSize += content.size(); // ----- Content-type headers = curl_slist_append(headers, ("Content-type: " + content_type).c_str()); outgoingMsgSize += content_type.size() + 14; // from "Content-type: " // Fiware-Correlator std::string correlation = "Fiware-Correlator: " + fiwareCorrelation; headers = curl_slist_append(headers, correlation.c_str()); outgoingMsgSize += correlation.size(); // Notify Format if ((ngisv2AttrFormat != "") && (ngisv2AttrFormat != "JSON") && (ngisv2AttrFormat != "legacy")) { std::string nFormat = "X-Ngsiv2-AttrsFormat: " + ngisv2AttrFormat; headers = curl_slist_append(headers, nFormat.c_str()); outgoingMsgSize += nFormat.size(); } // Check if total outgoing message size is too big if (outgoingMsgSize > MAX_DYN_MSG_SIZE) { LM_E(("Runtime Error (HTTP request to send is too large: %d bytes)", outgoingMsgSize)); curl_slist_free_all(headers); free(httpResponse->memory); delete httpResponse; lmTransactionEnd(); *outP = "error"; return -7; } // Contents const char* payload = content.c_str(); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (u_int8_t*) payload); // Set up URL std::string url; if (isIPv6(ip)) url = "[" + ip + "]"; else url = ip; url = url + ":" + portAsString + (resource.at(0) == '/'? "" : "/") + resource; // Prepare CURL handle with obtained options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb.c_str()); // Set HTTP verb curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Allow redirection (?) curl_easy_setopt(curl, CURLOPT_HEADER, 1); // Activate include the header in the body output curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Put headers in place curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeMemoryCallback); // Send data here curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) httpResponse); // Custom data for response handling // // There is a known problem in libcurl (see http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame) // which is solved using CURLOPT_NOSIGNAL. If we update some day from libcurl 7.19 (the one that comes with CentOS 6.x) to a newer version // (there are some reports about the bug is no longer in libcurl 7.32), using CURLOPT_NOSIGNAL could be not necessary and this be removed). // See issue #1016 for more details. // curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // // Timeout // // The parameter timeoutInMilliseconds holds the timeout time in milliseconds. // If the timeout time requested is 0, then no timeuot is used. // if (timeoutInMilliseconds != 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutInMilliseconds); } // Synchronous HTTP request // This was previously a LM_T trace, but we have "promoted" it to INFO due to it is needed to check logs in a .test case (case 000 notification_different_sizes.test) LM_I(("Sending message %lu to HTTP server: sending message of %d bytes to HTTP server", callNo, outgoingMsgSize)); res = curl_easy_perform(curl); if (res != CURLE_OK) { // // NOTE: This log line is used by the functional tests in cases/880_timeout_for_forward_and_notifications/ // So, this line should not be removed/altered, at least not without also modifying the functests. // alarmMgr.notificationError(url, "(curl_easy_perform failed: " + std::string(curl_easy_strerror(res)) + ")"); *outP = "notification failure"; } else { // The Response is here LM_I(("Notification Successfully Sent to %s", url.c_str())); outP->assign(httpResponse->memory, httpResponse->size); } // Cleanup curl environment curl_slist_free_all(headers); free(httpResponse->memory); delete httpResponse; lmTransactionEnd(); return res == CURLE_OK ? 0 : -9; }
/* **************************************************************************** * * mongoConnect - * * Default value for writeConcern == 1 (0: unacknowledged, 1: acknowledged) */ static DBClientBase* mongoConnect ( const char* host, const char* db, const char* rplSet, const char* username, const char* passwd, bool multitenant, int writeConcern, double timeout ) { std::string err; DBClientBase* connection = NULL; LM_T(LmtMongo, ("Connection info: dbName='%s', rplSet='%s', timeout=%f", db, rplSet, timeout)); bool connected = false; int retries = RECONNECT_RETRIES; if (strlen(rplSet) == 0) { // Setting the first argument to true is to use autoreconnect connection = new DBClientConnection(true); // // Not sure of how to generalize the following code, // given that DBClientBase class doesn't have a common connect() method (surprisingly) // for (int tryNo = 0; tryNo < retries; ++tryNo) { if ( ((DBClientConnection*)connection)->connect(host, err)) { connected = true; break; } if (tryNo == 0) { LM_E(("Database Startup Error (cannot connect to mongo - doing %d retries with a %d microsecond interval)", retries, RECONNECT_DELAY)); } else { LM_T(LmtMongo, ("Try %d connecting to mongo failed", tryNo)); } usleep(RECONNECT_DELAY * 1000); // usleep accepts microseconds } } else { LM_T(LmtMongo, ("Using replica set %s", rplSet)); // autoReconnect is always on for DBClientReplicaSet connections. std::vector<std::string> hostTokens; int components = stringSplit(host, ',', hostTokens); std::vector<HostAndPort> rplSetHosts; for (int ix = 0; ix < components; ix++) { LM_T(LmtMongo, ("rplSet host <%s>", hostTokens[ix].c_str())); rplSetHosts.push_back(HostAndPort(hostTokens[ix])); } connection = new DBClientReplicaSet(rplSet, rplSetHosts, timeout); // // Not sure of to generalize the following code, // given that DBClientBase class hasn't a common connect() method (surprisingly) // for (int tryNo = 0; tryNo < retries; ++tryNo) { if ( ((DBClientReplicaSet*)connection)->connect()) { connected = true; break; } if (tryNo == 0) { LM_E(("Database Startup Error (cannot connect to mongo - doing %d retries with a %d microsecond interval)", retries, RECONNECT_DELAY)); } else { LM_T(LmtMongo, ("Try %d connecting to mongo failed", tryNo)); } usleep(RECONNECT_DELAY * 1000); // usleep accepts microseconds } } if (connected == false) { LM_E(("Database Error (connection failed, after %d retries: '%s')", retries, err.c_str())); return NULL; } LM_I(("Successful connection to database")); // // WriteConcern // mongo::WriteConcern writeConcernCheck; // // In the legacy driver, writeConcern is no longer an int, but a class. // We need a small conversion step here // mongo::WriteConcern wc = writeConcern == 1 ? mongo::WriteConcern::acknowledged : mongo::WriteConcern::unacknowledged; setWriteConcern(connection, wc, &err); getWriteConcern(connection, &writeConcernCheck, &err); if (writeConcernCheck.nodes() != wc.nodes()) { LM_E(("Database Error (Write Concern not set as desired)")); return NULL; } LM_T(LmtMongo, ("Active DB Write Concern mode: %d", writeConcern)); /* Authentication is different depending if multiservice is used or not. In the case of not * using multiservice, we authenticate in the single-service database. In the case of using * multiservice, it isn't a default database that we know at contextBroker start time (when * this connection function is invoked) so we authenticate on the admin database, which provides * access to any database */ if (multitenant) { if (strlen(username) != 0 && strlen(passwd) != 0) { if (!connectionAuth(connection, "admin", std::string(username), std::string(passwd), &err)) { return NULL; } } } else { if (strlen(db) != 0 && strlen(username) != 0 && strlen(passwd) != 0) { if (!connectionAuth(connection, std::string(db), std::string(username), std::string(passwd), &err)) { return NULL; } } } /* Get mongo version with the 'buildinfo' command */ BSONObj result; std::string extra; runCollectionCommand(connection, "admin", BSON("buildinfo" << 1), &result, &err); std::string versionString = std::string(getStringField(result, "version")); if (!versionParse(versionString, mongoVersionMayor, mongoVersionMinor, extra)) { LM_E(("Database Startup Error (invalid version format: %s)", versionString.c_str())); return NULL; } LM_T(LmtMongo, ("mongo version server: %s (mayor: %d, minor: %d, extra: %s)", versionString.c_str(), mongoVersionMayor, mongoVersionMinor, extra.c_str())); return connection; }
/* **************************************************************************** * * mongoEntityTypes - */ HttpStatusCode mongoEntityTypes ( EntityTypesResponse* responseP, const std::string& tenant, const std::vector<std::string>& servicePathV, std::map<std::string, std::string>& uriParams ) { unsigned int offset = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str()); unsigned int limit = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str()); std::string detailsString = uriParams[URI_PARAM_PAGINATION_DETAILS]; bool details = (strcasecmp("on", detailsString.c_str()) == 0)? true : false; LM_T(LmtMongo, ("Query Entity Types")); LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false")); reqSemTake(__FUNCTION__, "query types request"); DBClientBase* connection = getMongoConnection(); /* Compose query based on this aggregation command: * * FIXME P9: taking into account that type is no longer used as part of the attribute "key", not sure if the * aggregation query below is fully correct * * db.runCommand({aggregate: "entities", * pipeline: [ {$project: {_id: 1, "attrs.name": 1, "attrs.type": 1} }, * {$unwind: "$attrs"}, * {$group: {_id: "$_id.type", attrs: {$addToSet: "$attrs"}} }, * {$sort: {_id: 1} } * ] * }) * * FIXME P6: in the future, we can interpret the collapse parameter at this layer. If collapse=true so we don't need attributes, the * following command can be used: * * db.runCommand({aggregate: "entities", pipeline: [ {$group: {_id: "$_id.type"} }]}) * */ BSONObj result; BSONObj cmd = BSON("aggregate" << COL_ENTITIES << "pipeline" << BSON_ARRAY( BSON("$project" << BSON("_id" << 1 << C_ATTR_NAME << 1 << C_ATTR_TYPE << 1)) << BSON("$unwind" << S_ATTRS) << BSON("$group" << BSON("_id" << CS_ID_ENTITY << "attrs" << BSON("$addToSet" << S_ATTRS))) << BSON("$sort" << BSON("_id" << 1)) ) ); LM_T(LmtMongo, ("runCommand() in '%s' database: '%s'", composeDatabaseName(tenant).c_str(), cmd.toString().c_str())); mongoSemTake(__FUNCTION__, "aggregation command"); try { connection->runCommand(composeDatabaseName(tenant).c_str(), cmd, result); mongoSemGive(__FUNCTION__, "aggregation command"); LM_I(("Database Operation Successful (%s)", cmd.toString().c_str())); } catch (const DBException& e) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + e.what(); LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } catch (...) { mongoSemGive(__FUNCTION__, "aggregation command"); std::string err = std::string("database: ") + composeDatabaseName(tenant).c_str() + " - command: " + cmd.toString() + " - exception: " + "generic"; LM_E(("Database Error (%s)", err.c_str())); responseP->statusCode.fill(SccReceiverInternalError, err); reqSemGive(__FUNCTION__, "query types request"); return SccOk; } /* Processing result to build response*/ LM_T(LmtMongo, ("aggregation result: %s", result.toString().c_str())); std::vector<BSONElement> resultsArray = result.getField("result").Array(); /* Another strategy to implement pagination is to use the $skip and $limit operators in the * aggregation framework. However, doing so, we don't know the total number of results, which can * be needed in the case of details=on (using that approach, we need to do two queries: one to get * the count and other to get the actual results with $skip and $limit, in the same "transaction" to * avoid incoherence between both if some entity type is created or deleted in the process). * * However, considering that the number of types will be small compared with the number of entities, * the current approach seems to be ok */ for (unsigned int ix = offset; ix < MIN(resultsArray.size(), offset + limit); ++ix) { BSONObj resultItem = resultsArray[ix].embeddedObject(); TypeEntity* type = new TypeEntity(resultItem.getStringField("_id")); std::vector<BSONElement> attrsArray = resultItem.getField("attrs").Array(); for (unsigned int jx = 0; jx < attrsArray.size(); ++jx) { BSONObj jAttr = attrsArray[jx].embeddedObject(); ContextAttribute* ca = new ContextAttribute(jAttr.getStringField(ENT_ATTRS_NAME), jAttr.getStringField(ENT_ATTRS_TYPE)); type->contextAttributeVector.push_back(ca); } responseP->typeEntityVector.push_back(type); } char detailsMsg[256]; if (responseP->typeEntityVector.size() > 0) { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Count: %d", (int) resultsArray.size()); responseP->statusCode.fill(SccOk, detailsMsg); } else { responseP->statusCode.fill(SccOk); } } else { if (details) { snprintf(detailsMsg, sizeof(detailsMsg), "Number of types: %d. Offset is %d", (int) resultsArray.size(), offset); responseP->statusCode.fill(SccContextElementNotFound, detailsMsg); } else { responseP->statusCode.fill(SccContextElementNotFound); } } reqSemGive(__FUNCTION__, "query types request"); return SccOk; }
/* **************************************************************************** * * attributeType - * */ static std::string attributeType ( const std::string& tenant, const std::vector<std::string>& servicePathV, const std::string entityType, const std::string attrName ) { std::string idType = std::string("_id.") + ENT_ENTITY_TYPE; std::string idServicePath = std::string("_id.") + ENT_SERVICE_PATH; std::string attributeName = std::string(ENT_ATTRS) + "." + attrName; BSONObj query = BSON(idType << entityType << idServicePath << fillQueryServicePath(servicePathV) << attributeName << BSON("$exists" << true)); std::auto_ptr<DBClientCursor> cursor; DBClientBase* connection = NULL; LM_T(LmtMongo, ("query() in '%s' collection: '%s'", getEntitiesCollectionName(tenant).c_str(), query.toString().c_str())); try { connection = getMongoConnection(); cursor = connection->query(getEntitiesCollectionName(tenant).c_str(), query); /* * We have observed that in some cases of DB errors (e.g. the database daemon is down) instead of * raising an exception, the query() method sets the cursor to NULL. In this case, we raise the * exception ourselves */ if (cursor.get() == NULL) { throw DBException("Null cursor from mongo (details on this is found in the source code)", 0); } releaseMongoConnection(connection); LM_I(("Database Operation Successful (%s)", query.toString().c_str())); } catch (const DBException &e) { releaseMongoConnection(connection); LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), e.what())); return ""; } catch (...) { releaseMongoConnection(connection); LM_E(("Database Error ('%s', '%s')", query.toString().c_str(), "generic exception")); return ""; } while (cursor->more()) { BSONObj r = cursor->next(); LM_T(LmtMongo, ("retrieved document: '%s'", r.toString().c_str())); /* It could happen that different entities within the same entity type may have attributes with the same name * but different types. In that case, one type (at random) is returned. A list could be returned but the * NGSIv2 operations only allow to set one type */ return r.getField(ENT_ATTRS).embeddedObject().getField(attrName).embeddedObject().getStringField(ENT_ATTRS_TYPE); } return ""; }
/* **************************************************************************** * * httpRequestSend - * * The waitForResponse arguments specifies if the method has to wait for response * before return. If this argument is false, the return string is "" * * FIXME: I don't like too much "reusing" natural output to return "error" in the * case of error. I think it would be smarter to use "std::string* error" in the * arguments or (even better) an exception. To be solved in the future in a hardening * period. * * Note, we are using a hybrid approach, consisting in an static thread-local buffer of * small size that copes with most notifications to avoid expensive * calloc/free syscalls if the notification payload is not very large. * */ std::string httpRequestSend ( const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& resource, const std::string& content_type, const std::string& content, bool useRush, bool waitForResponse, const std::string& acceptFormat, int timeoutInMilliseconds ) { char buffer[TAM_BUF]; char response[TAM_BUF]; char preContent[TAM_BUF]; char msgStatic[MAX_STA_MSG_SIZE]; char* what = (char*) "static"; char* msgDynamic = NULL; char* msg = msgStatic; // by default, use the static buffer std::string rushHeaderIP = ""; unsigned short rushHeaderPort = 0; std::string rushHttpHeaders = ""; static unsigned long long callNo = 0; std::string result; std::string ip = _ip; ++callNo; // Preconditions check if (port == 0) { LM_E(("Runtime Error (port is ZERO)")); return "error"; } if (ip.empty()) { LM_E(("Runtime Error (ip is empty)")); return "error"; } if (verb.empty()) { LM_E(("Runtime Error (verb is empty)")); return "error"; } if (resource.empty()) { LM_E(("Runtime Error (resource is empty)")); return "error"; } if ((content_type.empty()) && (!content.empty())) { LM_E(("Runtime Error (Content-Type is empty but there is actual content)")); return "error"; } if ((!content_type.empty()) && (content.empty())) { LM_E(("Runtime Error (Content-Type non-empty but there is no content)")); return "error"; } if (timeoutInMilliseconds == -1) { timeoutInMilliseconds = defaultTimeout; } // // Rush // Every call to httpRequestSend specifies whether RUSH should be used or not. // But, this depends also on how the broker was started, so here the 'useRush' // parameter is cancelled in case the broker was started without rush. // // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and // after that, the host and port of rush is set as ip/port for the message, that is // now sent to rush instead of to its final destination. // Also, a few HTTP headers for rush must be setup. // if (useRush) { if ((rushPort == 0) || (rushHost == "")) useRush = false; else { char portAsString[16]; rushHeaderIP = ip; rushHeaderPort = port; ip = rushHost; port = rushPort; sprintf(portAsString, "%d", (int) rushHeaderPort); rushHttpHeaders = "X-relayer-host: " + rushHeaderIP + ":" + portAsString + "\n"; if (protocol == "https:") rushHttpHeaders += "X-relayer-protocol: https\n"; } } // Buffers clear memset(buffer, 0, TAM_BUF); memset(response, 0, TAM_BUF); memset(msg, 0, MAX_STA_MSG_SIZE); char cvBuf[128]; // // Accepted mime-types // if (acceptFormat == "") acceptFormat = "application/xml, application/json"; snprintf(preContent, sizeof(preContent), "%s %s HTTP/1.1\n" "User-Agent: orion/%s libcurl/%s\n" "Host: %s:%d\n" "Accept: %s\n%s", verb.c_str(), resource.c_str(), versionGet(), curlVersionGet(cvBuf, sizeof(cvBuf)), ip.c_str(), (int) port, acceptFormat.c_str(), rushHttpHeaders.c_str()); LM_T(LmtRush, ("'PRE' HTTP headers:\n--------------\n%s\n-------------", preContent)); if (tenant != "") { char tenantHeader[128]; snprintf(tenantHeader, sizeof(tenantHeader), "fiware-service: %s\n", tenant.c_str()); strncat(preContent, tenantHeader, sizeof(preContent) - strlen(preContent)); } if (!content.empty()) { char headers[512]; sprintf(headers, "Content-Type: %s\n" "Content-Length: %zu\n", content_type.c_str(), content.length() + 1); strncat(preContent, headers, sizeof(preContent) - strlen(preContent)); /* Choose the right buffer (static or dynamic) to use. Note we are using +3 due to: * + 1, for the \n between preContent and content * + 1, for the \n at the end of the message * + 1, for the \0 by the trailing character in C strings */ int neededSize = content.length() + strlen(preContent) + 3; if (neededSize > MAX_DYN_MSG_SIZE) { LM_E(("Runtime Error (HTTP request to send is too large: %d bytes)", content.length() + strlen(preContent))); return "error"; } else if (neededSize > MAX_STA_MSG_SIZE) { msgDynamic = (char*) calloc(sizeof(char), neededSize); if (msgDynamic == NULL) { LM_E(("Runtime Error (dynamic memory allocation failure)")); return "error"; } msg = msgDynamic; what = (char*) "dynamic"; } /* The above checking should ensure that the three parts fit, so we are using * sprint() instead of snprintf() */ sprintf(msg, "%s\n%s", preContent, content.c_str()); } else { /* In the case of no-content we assume that MAX_STA_MSG_SIZE is enough to send the message */ LM_T(LmtClientOutputPayload, ("Using static buffer to send HTTP request (empty content)")); sprintf(msg, "%s\n", preContent); } /* We add a final newline (I guess that HTTP protocol needs it) */ strcat(msg, "\n"); int fd = httpRequestConnect(ip, port); // Connecting to HTTP server if (fd == -1) { return "error"; } int nb; int sz = strlen(msg); LM_T(LmtClientOutputPayload, ("Sending message %lu to HTTP server: sending %s message of %d bytes to HTTP server", callNo, what, sz)); LM_T(LmtClientOutputPayloadDump, ("Sending to HTTP server payload:\n%s", msg)); nb = send(fd, msg, sz, 0); if (msgDynamic != NULL) { free(msgDynamic); } if (nb == -1) { LM_W(("Notification failure for %s:%d (send: %s)", _ip.c_str(), port, strerror(errno))); return "error"; } else if (nb != sz) { LM_W(("Notification failure for %s:%d (not entire message sent)", _ip.c_str(), port)); return "error"; } if (waitForResponse) { nb = recv(fd, &buffer, TAM_BUF - 1, 0); if (nb == -1) { LM_W(("Notification failure for %s:%d (error receiving ACK from HTTP server: %s)", _ip.c_str(), port, strerror(errno))); return "error"; } else if ( nb >= TAM_BUF) { LM_W(("Notification failure for %s:%d (message size of HTTP server reply is too big: %d (max allowed %d)) ", _ip.c_str(), port, nb, TAM_BUF)); return "error"; } else { memcpy(response, buffer, nb); LM_I(("Notification Successfully Sent to %s:%d%s", ip.c_str(), port, resource.c_str())); LM_T(LmtClientInputPayload, ("Received from HTTP server:\n%s", response)); } if (strlen(response) > 0) result = response; } else { LM_T(LmtClientInputPayload, ("not waiting for response")); LM_I(("Notification Successfully Sent to %s:%d%s", ip.c_str(), port, resource.c_str())); result = ""; } close(fd); return result; }
/* **************************************************************************** * * httpRequestSend - */ std::string httpRequestSend ( const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& servicePath, const std::string& xauthToken, const std::string& resource, const std::string& content_type, const std::string& content, bool useRush, bool waitForResponse, const std::string& acceptFormat, long timeoutInMilliseconds ) { char portAsString[16]; static unsigned long long callNo = 0; std::string result; std::string ip = _ip; struct curl_slist* headers = NULL; MemoryStruct* httpResponse = NULL; CURLcode res; int outgoingMsgSize = 0; CURL* curl; ++callNo; if (timeoutInMilliseconds == -1) { timeoutInMilliseconds = defaultTimeout; } LM_TRANSACTION_START("to", ip.c_str(), port, resource.c_str()); // Preconditions check if (port == 0) { LM_E(("Runtime Error (port is ZERO)")); LM_TRANSACTION_END(); return "error"; } if (ip.empty()) { LM_E(("Runtime Error (ip is empty)")); LM_TRANSACTION_END(); return "error"; } if (verb.empty()) { LM_E(("Runtime Error (verb is empty)")); LM_TRANSACTION_END(); return "error"; } if (resource.empty()) { LM_E(("Runtime Error (resource is empty)")); LM_TRANSACTION_END(); return "error"; } if ((content_type.empty()) && (!content.empty())) { LM_E(("Runtime Error (Content-Type is empty but there is actual content)")); LM_TRANSACTION_END(); return "error"; } if ((!content_type.empty()) && (content.empty())) { LM_E(("Runtime Error (Content-Type non-empty but there is no content)")); LM_TRANSACTION_END(); return "error"; } if ((curl = curl_easy_init()) == NULL) { LM_E(("Runtime Error (could not init libcurl)")); LM_TRANSACTION_END(); return "error"; } // Allocate to hold HTTP response httpResponse = new MemoryStruct; httpResponse->memory = (char*) malloc(1); // will grow as needed httpResponse->size = 0; // no data at this point // // Rush // Every call to httpRequestSend specifies whether RUSH should be used or not. // But, this depends also on how the broker was started, so here the 'useRush' // parameter is cancelled in case the broker was started without rush. // // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and // after that, the host and port of rush is set as ip/port for the message, that is // now sent to rush instead of to its final destination. // Also, a few HTTP headers for rush must be setup. // if ((rushPort == 0) || (rushHost == "")) useRush = false; if (useRush) { char rushHeaderPortAsString[16]; uint16_t rushHeaderPort = 0; std::string rushHeaderIP = ""; std::string headerRushHttp = ""; rushHeaderIP = ip; rushHeaderPort = port; ip = rushHost; port = rushPort; snprintf(rushHeaderPortAsString, sizeof(rushHeaderPortAsString), "%d", rushHeaderPort); headerRushHttp = "X-relayer-host: " + rushHeaderIP + ":" + rushHeaderPortAsString; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); if (protocol == "https:") { headerRushHttp = "X-relayer-protocol: https"; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); } } snprintf(portAsString, sizeof(portAsString), "%d", port); // ----- User Agent char cvBuf[CURL_VERSION_MAX_LENGTH]; char headerUserAgent[HTTP_HEADER_USER_AGENT_MAX_LENGTH]; snprintf(headerUserAgent, sizeof(headerUserAgent), "User-Agent: orion/%s libcurl/%s", versionGet(), curlVersionGet(cvBuf, sizeof(cvBuf))); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerUserAgent)); headers = curl_slist_append(headers, headerUserAgent); outgoingMsgSize += strlen(headerUserAgent) + 1; // ----- Host char headerHost[HTTP_HEADER_HOST_MAX_LENGTH]; snprintf(headerHost, sizeof(headerHost), "Host: %s:%d", ip.c_str(), (int) port); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerHost)); headers = curl_slist_append(headers, headerHost); outgoingMsgSize += strlen(headerHost) + 1; // ----- Tenant if (tenant != "") { headers = curl_slist_append(headers, ("fiware-service: " + tenant).c_str()); outgoingMsgSize += tenant.size() + 16; // "fiware-service: " } // ----- Service-Path if (servicePath != "") { headers = curl_slist_append(headers, ("Fiware-ServicePath: " + servicePath).c_str()); outgoingMsgSize += servicePath.size() + strlen("Fiware-ServicePath: "); } // ----- X-Auth-Token if (xauthToken != "") { headers = curl_slist_append(headers, ("X-Auth-Token: " + xauthToken).c_str()); outgoingMsgSize += xauthToken.size() + strlen("X-Auth-Token: "); } // ----- Accept std::string acceptedFormats = "application/xml, application/json"; if (acceptFormat != "") { acceptedFormats = acceptFormat; } std::string acceptString = "Accept: " + acceptedFormats; headers = curl_slist_append(headers, acceptString.c_str()); outgoingMsgSize += acceptString.size(); // ----- Expect headers = curl_slist_append(headers, "Expect: "); outgoingMsgSize += 8; // from "Expect: " // ----- Content-length std::stringstream contentLengthStringStream; contentLengthStringStream << content.size(); std::string headerContentLength = "Content-length: " + contentLengthStringStream.str(); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerContentLength.c_str())); headers = curl_slist_append(headers, headerContentLength.c_str()); outgoingMsgSize += contentLengthStringStream.str().size() + 16; // from "Content-length: " outgoingMsgSize += content.size(); // ----- Content-type headers = curl_slist_append(headers, ("Content-type: " + content_type).c_str()); outgoingMsgSize += content_type.size() + 14; // from "Content-type: " // Check if total outgoing message size is too big if (outgoingMsgSize > MAX_DYN_MSG_SIZE) { LM_E(("Runtime Error (HTTP request to send is too large: %d bytes)", outgoingMsgSize)); // Cleanup curl environment curl_slist_free_all(headers); curl_easy_cleanup(curl); free(httpResponse->memory); delete httpResponse; LM_TRANSACTION_END(); return "error"; } // Contents const char* payload = content.c_str(); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (u_int8_t*) payload); // Set up URL std::string url; if (isIPv6(ip)) url = "[" + ip + "]"; else url = ip; url = url + ":" + portAsString + (resource.at(0) == '/'? "" : "/") + resource; // Prepare CURL handle with obtained options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb.c_str()); // Set HTTP verb curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Allow redirection (?) curl_easy_setopt(curl, CURLOPT_HEADER, 1); // Activate include the header in the body output curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Put headers in place curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeMemoryCallback); // Send data here curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) httpResponse); // Custom data for response handling // // Timeout // // The parameter timeoutInMilliseconds holds the timeout time in milliseconds. // If the timeout time requested is 0, then no timeuot is used. // if (timeoutInMilliseconds != 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutInMilliseconds); } // Synchronous HTTP request LM_T(LmtClientOutputPayload, ("Sending message %lu to HTTP server: sending message of %d bytes to HTTP server", callNo, outgoingMsgSize)); res = curl_easy_perform(curl); if (res != CURLE_OK) { // // NOTE: This log line is used by the functional tests in cases/880_timeout_for_forward_and_notifications/ // So, this line should not be removed/altered, at least not without also modifying the functests. // LM_W(("Notification failure for %s:%s (curl_easy_perform failed: %s)", ip.c_str(), portAsString, curl_easy_strerror(res))); result = ""; } else { // The Response is here LM_I(("Notification Successfully Sent to %s", url.c_str())); result.assign(httpResponse->memory, httpResponse->size); } // Cleanup curl environment curl_slist_free_all(headers); curl_easy_cleanup(curl); free(httpResponse->memory); delete httpResponse; LM_TRANSACTION_END(); return result; }