/************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2018 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ //------------------------------------------------------------------------------ //! @author Georgios Bitzes //! @brief Class to retrieve metadata from the backend - no caching! //------------------------------------------------------------------------------ #include #include "namespace/interface/IFileMD.hh" #include "namespace/ns_quarkdb/persistency/MetadataFetcher.hh" #include "namespace/ns_quarkdb/persistency/ContainerMDSvc.hh" #include "namespace/ns_quarkdb/persistency/FileMDSvc.hh" #include "namespace/ns_quarkdb/persistency/Serialization.hh" #include "namespace/ns_quarkdb/persistency/RequestBuilder.hh" #include "namespace/utils/PathProcessor.hh" #include "qclient/QClient.hh" #define SSTR(message) static_cast(std::ostringstream().flush() << message).str() #define DBG(message) std::cerr << __FILE__ << ":" << __LINE__ << " -- " \ << #message << " = " << message << std::endl using redisReplyPtr = qclient::redisReplyPtr; using std::placeholders::_1; EOSNSNAMESPACE_BEGIN //------------------------------------------------------------------------------ //! Helper method to check that the redis reply is a string reply //! //! @param reply redis reply //------------------------------------------------------------------------------ MDStatus ensureStringReply(redisReplyPtr& reply) { if (!reply) { return MDStatus(EFAULT, "QuarkDB backend not available!"); } if ((reply->type == REDIS_REPLY_NIL) || (reply->type == REDIS_REPLY_STRING && reply->len == 0)) { return MDStatus(ENOENT, "Empty response"); } if (reply->type != REDIS_REPLY_STRING) { return MDStatus(EFAULT, SSTR("Received unexpected response, was expecting string: " << qclient::describeRedisReply(reply))); } return MDStatus(); } //------------------------------------------------------------------------------ // Helper method to check that a redis reply is 0/1 //------------------------------------------------------------------------------ MDStatus ensureBoolReply(redisReplyPtr& reply) { if (!reply) { return MDStatus(EFAULT, "QuarkDB backend not available!"); } if (reply->type != REDIS_REPLY_INTEGER) { return MDStatus(EFAULT, SSTR("Received unexpected response, was expecting integer: " << qclient::describeRedisReply(reply))); } if (reply->integer != 0 && reply->integer != 1) { return MDStatus(EFAULT, SSTR("Received unexpected integer, was expecting {0,1}: " << qclient::describeRedisReply(reply))); } return MDStatus(); } //------------------------------------------------------------------------------ // Helper method to check that a redis reply is uint64_t //------------------------------------------------------------------------------ MDStatus ensureUInt64Reply(redisReplyPtr& reply) { if (!reply) { return MDStatus(EFAULT, "QuarkDB backend not available!"); } if (reply->type != REDIS_REPLY_INTEGER) { return MDStatus(EFAULT, SSTR("Received unexpected response, was expecting integer: " << qclient::describeRedisReply(reply))); } if (reply->integer < 0) { return MDStatus(EFAULT, SSTR("Received unexpected value, was expecting a uint64_t: " << qclient::describeRedisReply(reply))); } return MDStatus(); } //------------------------------------------------------------------------------ //! Struct MapFetcherFileTrait //------------------------------------------------------------------------------ struct MapFetcherFileTrait { static std::string getKey(IContainerMD::id_t id) { return SSTR(id << constants::sMapFilesSuffix); } using ContainerType = IContainerMD::FileMap; }; //------------------------------------------------------------------------------ //! Struct MapFetcherContainerTrait //------------------------------------------------------------------------------ struct MapFetcherContainerTrait { static std::string getKey(IFileMD::id_t id) { return SSTR(id << constants::sMapDirsSuffix); } using ContainerType = IContainerMD::ContainerMap; }; //------------------------------------------------------------------------------ //! Class MapFetcher - fetches maps (ContainerMap, FileMap) of a particular //! container. //------------------------------------------------------------------------------ template class MapFetcher : public qclient::QCallback { public: using ContainerType = typename Trait::ContainerType; static constexpr size_t kCount = 250000; //---------------------------------------------------------------------------- //! Constructor //---------------------------------------------------------------------------- MapFetcher() { mContents.set_deleted_key(""); mContents.set_empty_key("##_EMPTY_##"); } //---------------------------------------------------------------------------- //! Initialize //! //! @param qcl qclient object //! @param trg target container id //---------------------------------------------------------------------------- folly::Future initialize(qclient::QClient& qcl, ContainerIdentifier trg) { mQcl = &qcl; mTarget = trg; folly::Future fut = mPromise.getFuture(); // There's a particularly evil race condition here: From the point we call // execCB and onwards, we must assume that the callback has arrived, // and *this has been destroyed already. // Not safe to access member variables after execCB, so we fetch the future // beforehand. mQcl->execCB(this, "HSCAN", Trait::getKey(mTarget.getUnderlyingUInt64()), "0", "COUNT", SSTR(kCount)); // fut is a stack object, safe to access. return fut; } //---------------------------------------------------------------------------- //! Handle response //! //! @param reply holds a redis reply object //---------------------------------------------------------------------------- virtual void handleResponse(redisReplyPtr&& reply) override { if (!reply) { return set_exception(EFAULT, "QuarkDB backend not available!"); } if (reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 || (reply->element[0]->type != REDIS_REPLY_STRING) || (reply->element[1]->type != REDIS_REPLY_ARRAY) || ((reply->element[1]->elements % 2) != 0)) { return set_exception(EFAULT, SSTR("Received unexpected response: " << qclient::describeRedisReply(reply))); } std::string cursor = std::string(reply->element[0]->str, reply->element[0]->len); for (size_t i = 0; i < reply->element[1]->elements; i += 2) { redisReply* element = reply->element[1]->element[i]; if (element->type != REDIS_REPLY_STRING) { return set_exception(EFAULT, SSTR("Received unexpected response: " << qclient::describeRedisReply(reply))); } std::string filename = std::string(element->str, element->len); element = reply->element[1]->element[i + 1]; if (element->type != REDIS_REPLY_STRING) { return set_exception(EFAULT, SSTR("Received unexpected response: " << qclient::describeRedisReply(reply))); } int64_t value; MDStatus st = Serialization::deserialize(element->str, element->len, value); if (!st.ok()) { return set_exception(st); } mContents.insert(std::make_pair(filename, value)); } // Fire off next request? if (cursor == "0") { mPromise.setValue(std::move(mContents)); delete this; return; } mQcl->execCB(this, "HSCAN", Trait::getKey(mTarget.getUnderlyingUInt64()), cursor, "COUNT", SSTR(kCount)); } private: //---------------------------------------------------------------------------- //! Return exception by passing it to the promise //! //! @param status error return status //---------------------------------------------------------------------------- void set_exception(const MDStatus& status) { return set_exception(status.getErrno(), status.getError()); } //---------------------------------------------------------------------------- //! Return exception by passing it to the promise //! //! @param status error return status //---------------------------------------------------------------------------- void set_exception(int err, const std::string& msg) { mPromise.setException( make_mdexception(err, SSTR("Error while fetching file/container map for " "container #" << mTarget.getUnderlyingUInt64() << " from QDB: " << msg))); delete this; // harakiri } qclient::QClient* mQcl; ContainerIdentifier mTarget; ContainerType mContents; folly::Promise mPromise; }; //------------------------------------------------------------------------------ // Parse FileMDProto from a redis response, throw on error. //------------------------------------------------------------------------------ static eos::ns::FileMdProto parseFileMdProtoResponse(redisReplyPtr reply, FileIdentifier id) { ensureStringReply(reply).throwIfNotOk(SSTR("Error while fetching FileMD #" << id.getUnderlyingUInt64() << " protobuf from QDB: ")); eos::ns::FileMdProto proto; Serialization::deserialize(reply->str, reply->len, proto) .throwIfNotOk(SSTR("Error while deserializing FileMD #" << id.getUnderlyingUInt64() << " protobuf: ")); return proto; } //------------------------------------------------------------------------------ // Fetch file metadata info for current id //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getFileFromId(qclient::QClient& qcl, FileIdentifier id) { return qcl.follyExec(RequestBuilder::readFileProto(id)) .thenValue(std::bind(parseFileMdProtoResponse, _1, id)); } //---------------------------------------------------------------------------- // Fetch file metadata info for current id //------------------------------------------------------------------------------ static bool checkFileMdProtoExistence(redisReplyPtr reply, FileIdentifier id) { MDStatus st = ensureStringReply(reply); if (st.getErrno() == ENOENT) { return false; } st.throwIfNotOk(SSTR("Error while fetching FileMD #" << id.getUnderlyingUInt64() << " protobuf from QDB: ")); return true; } //------------------------------------------------------------------------------ // Fetch container metadata info for current id //------------------------------------------------------------------------------ static bool checkContainerMdProtoExistence(redisReplyPtr reply, ContainerIdentifier id) { MDStatus st = ensureStringReply(reply); if (st.getErrno() == ENOENT) { return false; } st.throwIfNotOk(SSTR("Error while fetching ContainerMD #" << id.getUnderlyingUInt64() << " protobuf from QDB: ")); return true; } //---------------------------------------------------------------------------- // Check if given container id exists on the namespace //---------------------------------------------------------------------------- folly::Future MetadataFetcher::doesContainerMdExist(qclient::QClient& qcl, ContainerIdentifier id) { return qcl.follyExec(RequestBuilder::readContainerProto(id)) .thenValue(std::bind(checkContainerMdProtoExistence, _1, id)); } //---------------------------------------------------------------------------- // Check if given file id exists on the namespace //---------------------------------------------------------------------------- folly::Future MetadataFetcher::doesFileMdExist(qclient::QClient& qcl, FileIdentifier id) { return qcl.follyExec(RequestBuilder::readFileProto(id)) .thenValue(std::bind(checkFileMdProtoExistence, _1, id)); } //------------------------------------------------------------------------------ // Parse ContainerMdProto from a redis response, throw on error. //------------------------------------------------------------------------------ static eos::ns::ContainerMdProto parseContainerMdProtoResponse(redisReplyPtr reply, ContainerIdentifier id) { ensureStringReply(reply).throwIfNotOk(SSTR("Error while fetching ContainerMD #" << id.getUnderlyingUInt64() << " protobuf from QDB: ")); eos::ns::ContainerMdProto proto; Serialization::deserialize(reply->str, reply->len, proto) .throwIfNotOk(SSTR("Error while deserializing ContainerMd #" << id.getUnderlyingUInt64() << " protobuf: ")); return proto; } //------------------------------------------------------------------------------ // Fetch container metadata info for current id //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getContainerFromId(qclient::QClient& qcl, ContainerIdentifier id) { return qcl.follyExec(RequestBuilder::readContainerProto(id)) .thenValue(std::bind(parseContainerMdProtoResponse, _1, id)); } //------------------------------------------------------------------------------ // Class MetadataFetcher //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // Construct hmap key of subcontainers in container //------------------------------------------------------------------------------ std::string MetadataFetcher::keySubContainers(IContainerMD::id_t id) { return SSTR(id << constants::sMapDirsSuffix); } //------------------------------------------------------------------------------ // Construct hmap key of files in container //------------------------------------------------------------------------------ std::string MetadataFetcher::keySubFiles(IContainerMD::id_t id) { return SSTR(id << constants::sMapFilesSuffix); } //------------------------------------------------------------------------------ // Fetch all files for current id //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getFileMap(qclient::QClient& qcl, ContainerIdentifier container) { MapFetcher* fetcher = new MapFetcher(); return fetcher->initialize(qcl, container); } //------------------------------------------------------------------------------ // Fetch all file metadata within the given container. //------------------------------------------------------------------------------ folly::Future>> MetadataFetcher::getFileMDsInContainer(qclient::QClient& qcl, ContainerIdentifier container, folly::Executor* executor) { folly::Future filemap = getFileMap(qcl, container); return filemap.via(executor) .thenValue(std::bind(MetadataFetcher::getFilesFromFilemapV, std::ref(qcl), _1)); } //---------------------------------------------------------------------------- // Fetch all container metadata within the given container. //---------------------------------------------------------------------------- folly::Future>> MetadataFetcher::getContainerMDsInContainer(qclient::QClient& qcl, ContainerIdentifier container, folly::Executor* executor) { folly::Future containerMap = getContainerMap(qcl, container); return containerMap.via(executor) .thenValue(std::bind(MetadataFetcher::getContainersFromContainerMapV, std::ref(qcl), _1)); } //------------------------------------------------------------------------------ // Fetch all FileMDs contained within the given FileMap. Vector is sorted by // filename. //------------------------------------------------------------------------------ std::vector> MetadataFetcher::getFilesFromFilemap(qclient::QClient& qcl, const IContainerMD::FileMap& fileMap) { // FileMap is a hashmap, thus unsorted.. We want the results to be sorted // based on filename, though. std::map sortedFileMap; for (auto it = fileMap.begin(); it != fileMap.end(); ++it) { sortedFileMap[it->first] = it->second; } std::vector> retval; for (auto it = sortedFileMap.begin(); it != sortedFileMap.end(); it++) { retval.emplace_back(getFileFromId(qcl, FileIdentifier(it->second))); } return retval; } //------------------------------------------------------------------------------ // Same as above, but fileMap is passed as a value. //------------------------------------------------------------------------------ std::vector> MetadataFetcher::getFilesFromFilemapV(qclient::QClient& qcl, IContainerMD::FileMap fileMap) { return getFilesFromFilemap(qcl, fileMap); } //------------------------------------------------------------------------------ // Fetch all ContainerMDs contained within the given ContainerMap. // Vector is sorted by filename. //------------------------------------------------------------------------------ std::vector> MetadataFetcher::getContainersFromContainerMap(qclient::QClient& qcl, const IContainerMD::ContainerMap& containerMap) { // ContainerMap is a hashmap, thus unsorted.. We want the results to be // sorted based on filename, though. std::map sortedContainerMap; for (auto it = containerMap.begin(); it != containerMap.end(); ++it) { sortedContainerMap[it->first] = it->second; } std::vector> retval; for (auto it = sortedContainerMap.cbegin(); it != sortedContainerMap.cend(); it++) { retval.emplace_back(getContainerFromId(qcl, ContainerIdentifier(it->second))); } return retval; } //------------------------------------------------------------------------------ // Same as above, but containerMap is passed as a value. //------------------------------------------------------------------------------ std::vector> MetadataFetcher::getContainersFromContainerMapV(qclient::QClient& qcl, IContainerMD::ContainerMap containerMap) { return getContainersFromContainerMap(qcl, containerMap); } //------------------------------------------------------------------------------ // Fetch all subcontainers for current id //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getContainerMap(qclient::QClient& qcl, ContainerIdentifier container) { MapFetcher* fetcher = new MapFetcher(); return fetcher->initialize(qcl, container); } //------------------------------------------------------------------------------ // Parse response when looking up a ContainerID / FileID from (parent id, name) //------------------------------------------------------------------------------ static int64_t parseIDFromNameResponse(redisReplyPtr reply, ContainerIdentifier parentID, const std::string& name) { std::string errorPrefix = SSTR("Error while fetching FileID / ContainerID out of (parent id, name) = " "(" << parentID.getUnderlyingUInt64() << ", " << name << "): "); ensureStringReply(reply).throwIfNotOk(errorPrefix); int64_t retval; Serialization::deserialize(reply->str, reply->len, retval) .throwIfNotOk(errorPrefix); return retval; } //------------------------------------------------------------------------------ // int64_t -> FileIdentifier //------------------------------------------------------------------------------ static FileIdentifier convertInt64ToFileIdentifier(int64_t id) { return FileIdentifier(id); } //------------------------------------------------------------------------------ // int64_t -> ContainerIdentifier //------------------------------------------------------------------------------ static ContainerIdentifier convertInt64ToContainerIdentifier(int64_t id) { return ContainerIdentifier(id); } //------------------------------------------------------------------------------ // Fetch a file id given its parent and its name //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getFileIDFromName(qclient::QClient& qcl, ContainerIdentifier parent_id, const std::string& name) { return qcl.follyExec("HGET", SSTR(parent_id.getUnderlyingUInt64() << constants::sMapFilesSuffix), name) .thenValue(std::bind(parseIDFromNameResponse, _1, parent_id, name)) .thenValue(convertInt64ToFileIdentifier); } //------------------------------------------------------------------------------ // Fetch a container id given its parent and its name //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getContainerIDFromName(qclient::QClient& qcl, ContainerIdentifier parent_id, const std::string& name) { return qcl.follyExec("HGET", SSTR(parent_id.getUnderlyingUInt64() << constants::sMapDirsSuffix), name) .thenValue(std::bind(parseIDFromNameResponse, _1, parent_id, name)) .thenValue(convertInt64ToContainerIdentifier); } //------------------------------------------------------------------------------ // Resolve FileMdProto from parent ID + name //------------------------------------------------------------------------------ folly::Future MetadataFetcher::getFileFromName(qclient::QClient& qcl, ContainerIdentifier parent_id, const std::string& name) { return getFileIDFromName(qcl, parent_id, name) .thenValue(std::bind(getFileFromId, std::ref(qcl), _1)); } //---------------------------------------------------------------------------- //! Resolve ContainerMdProto from parent ID + name //---------------------------------------------------------------------------- folly::Future MetadataFetcher::getContainerFromName(qclient::QClient& qcl, ContainerIdentifier parent_id, const std::string& name) { return getContainerIDFromName(qcl, parent_id, name) .thenValue(std::bind(getContainerFromId, std::ref(qcl), _1)); } //------------------------------------------------------------------------------ // Parse bool response //------------------------------------------------------------------------------ bool parseBoolResponse(redisReplyPtr reply) { ensureBoolReply(reply).throwIfNotOk(""); if (reply->integer == 0) { return false; } return true; } //------------------------------------------------------------------------------ // Parse uint64_t response //------------------------------------------------------------------------------ uint64_t parseUInt64Response(redisReplyPtr reply) { ensureUInt64Reply(reply).throwIfNotOk(""); return reply->integer; } //------------------------------------------------------------------------------ // Is the given location of a FileID contained in the FsView? //------------------------------------------------------------------------------ folly::Future MetadataFetcher::locationExistsInFsView(qclient::QClient& qcl, FileIdentifier id, int64_t location, bool unlinked) { std::string key; if (!unlinked) { key = SSTR("fsview:" << location << ":files"); } else { key = SSTR("fsview:" << location << ":unlinked"); } return qcl.follyExec("SISMEMBER", key, SSTR(id.getUnderlyingUInt64())).thenValue(parseBoolResponse); } //------------------------------------------------------------------------------ // Helper class to resolve a Container's full path //------------------------------------------------------------------------------ class FullPathResolver : public qclient::QCallback { public: //---------------------------------------------------------------------------- // Constructor //---------------------------------------------------------------------------- FullPathResolver(qclient::QClient& qcl, ContainerIdentifier cont) : mQcl(qcl), mContainerID(cont) {} //---------------------------------------------------------------------------- // Initialize, fire off first request //---------------------------------------------------------------------------- folly::Future initialize() { folly::Future fut = mPromise.getFuture(); if (mContainerID == ContainerIdentifier(1)) { // Short-circuit lookup, return "/" set_value(); return fut; } mQcl.execCB(this, RequestBuilder::readContainerProto(mContainerID)); return fut; } //---------------------------------------------------------------------------- //! Handle response //---------------------------------------------------------------------------- virtual void handleResponse(redisReplyPtr&& reply) override { if (!reply) { return set_exception(EFAULT, "QuarkDB backend not available!"); } if (reply->type != REDIS_REPLY_STRING) { return set_exception(EFAULT, SSTR("Received unexpected response: " << qclient::describeRedisReply(reply))); } eos::ns::ContainerMdProto proto; MDStatus status = Serialization::deserialize(reply->str, reply->len, proto); if (!status.ok()) { return set_exception(status); } mPathStack.emplace_front(proto.name()); if (proto.parent_id() == 1) { // We're done return set_value(); } // Lookup next chunk mQcl.execCB(this, RequestBuilder::readContainerProto(ContainerIdentifier( proto.parent_id()))); } //---------------------------------------------------------------------------- // Return exception by passing it to the promise //---------------------------------------------------------------------------- void set_exception(const MDStatus& status) { return set_exception(status.getErrno(), status.getError()); } //---------------------------------------------------------------------------- // Return exception by passing it to the promise //---------------------------------------------------------------------------- void set_exception(int err, const std::string& msg) { mPromise.setException( make_mdexception(err, SSTR("Error while reconstructing full path of container #" << mContainerID.getUnderlyingUInt64() << " from QDB: " << msg))); delete this; // harakiri } //---------------------------------------------------------------------------- // Set value, we're done //---------------------------------------------------------------------------- void set_value() { std::ostringstream ss; ss << "/"; for (size_t i = 0; i < mPathStack.size(); i++) { ss << mPathStack[i] << "/"; } mPromise.setValue(ss.str()); delete this; } private: qclient::QClient& mQcl; ContainerIdentifier mContainerID; std::deque mPathStack; folly::Promise mPromise; }; //------------------------------------------------------------------------------ // Resolve container's full path. Throws an exception if this is a container // detached from "/". //------------------------------------------------------------------------------ folly::Future MetadataFetcher::resolveFullPath(qclient::QClient& qcl, ContainerIdentifier containerID) { FullPathResolver* resolver = new FullPathResolver(qcl, containerID); return resolver->initialize(); } //------------------------------------------------------------------------------ // Helper class to resolve a path to an ID - no symlink support yet. //------------------------------------------------------------------------------ class ReversePathResolver { public: //---------------------------------------------------------------------------- // Constructor //---------------------------------------------------------------------------- ReversePathResolver(qclient::QClient& qcl, const std::string& path) : mQcl(qcl), mPath(path) { eos::PathProcessor::insertChunksIntoDeque(mPathStack, path); } //---------------------------------------------------------------------------- // Initialize, fire off first request //---------------------------------------------------------------------------- folly::Future initialize() { folly::Future fut = mPromise.getFuture(); if (mPathStack.size() == 0) { set_value(ContainerIdentifier(1)); } else { startNextRound(ContainerIdentifier(1)); } return fut; } void handleIncomingFile(eos::ns::FileMdProto proto) { return set_value(FileIdentifier(proto.id())); } void handleIncomingContainer(eos::ns::ContainerMdProto proto) { mPathStack.pop_front(); if (mPathStack.empty()) { return set_value(ContainerIdentifier(proto.id())); } startNextRound(ContainerIdentifier(proto.id())); } void handleIncomingFileError(const folly::exception_wrapper& e) { mPromise.setException(e); delete this; // harakiri } void handleIncomingContainerError(ContainerIdentifier parent, const folly::exception_wrapper& e) { if (mPathStack.size() == 1) { MetadataFetcher::getFileFromName(mQcl, parent, mPathStack.front()) .thenValue(std::bind(&ReversePathResolver::handleIncomingFile, this, _1)) .thenError([this](const folly::exception_wrapper & e) { handleIncomingFileError(e); }); return; } mPromise.setException(e); delete this; // harakiri } //---------------------------------------------------------------------------- // Return exception by passing it to the promise //---------------------------------------------------------------------------- void set_exception(int err, const std::string& msg) { mPromise.setException( make_mdexception(err, SSTR("Error while resolving path " << mPath << " from QDB: " << msg))); delete this; // harakiri } //---------------------------------------------------------------------------- // Set value, we're done //---------------------------------------------------------------------------- void set_value(FileOrContainerIdentifier outcome) { mPromise.setValue(outcome); delete this; } private: //---------------------------------------------------------------------------- // Start next asynchronous round //---------------------------------------------------------------------------- void startNextRound(ContainerIdentifier parent) { MetadataFetcher::getContainerFromName(mQcl, parent, mPathStack.front()) .thenValue(std::bind(&ReversePathResolver::handleIncomingContainer, this, _1)) .thenError([this, parent](const folly::exception_wrapper & e) { handleIncomingContainerError(parent, e); }); } qclient::QClient& mQcl; std::string mPath; std::deque mPathStack; folly::Promise mPromise; }; //------------------------------------------------------------------------------ // Resolve a path to an ID. //------------------------------------------------------------------------------ folly::Future MetadataFetcher::resolvePathToID(qclient::QClient& qcl, const std::string& path) { ReversePathResolver* resolver = new ReversePathResolver(qcl, path); return resolver->initialize(); } //------------------------------------------------------------------------------ // Count how many files and containers are in the given directory //------------------------------------------------------------------------------ std::pair, folly::Future> MetadataFetcher::countContents(qclient::QClient& qcl, ContainerIdentifier containerID) { return std::make_pair, folly::Future>( qcl.follyExec("HLEN", keySubFiles(containerID.getUnderlyingUInt64())) .thenValue(parseUInt64Response), qcl.follyExec("HLEN", keySubContainers(containerID.getUnderlyingUInt64())) .thenValue(parseUInt64Response) ); } EOSNSNAMESPACE_END