diff --git a/CHANGELOG.md b/CHANGELOG.md index 513b2886d8379590dc38fb75f26e96c779108b8b..4ad0fbbd1894dee6468cb605e076da4c8df56fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ +## 20.03 (unreleased) FEATURES -* introduced substreams for producer/consumer [[JIRA_102](https://agira.desy.de/browse/HIDRA2-102)] +* introduced substreams for producer/consumer +* introduced timeout for producer requests IMPROVEMENTS * switch to MongoDB 4.2 * receiver use ASAP3 directory structure to save files to -* API documentation is available at [asapo-docs.desy.de](asapo-docs.desy.de) +* API documentation is available at * switch to using cmake 3.7+ * error messages in Python as Python strings, not byte objects BUG FIXES -* consumer operation timout - take duration of the operation into account \ No newline at end of file +* consumer operation timout - take duration of the operation into account +* giving warning/error on attempt to send data/metadata with same id \ No newline at end of file diff --git a/common/cpp/include/common/data_structs.h b/common/cpp/include/common/data_structs.h index 2fb0720d4fe1effad1038185831f50827124fcef..24b9649fd88064741372fa41422f5b79ac0b0c8c 100644 --- a/common/cpp/include/common/data_structs.h +++ b/common/cpp/include/common/data_structs.h @@ -41,6 +41,8 @@ using FileInfos = std::vector<FileInfo>; struct DataSet { uint64_t id; FileInfos content; + bool SetFromJson(const std::string& json_string); + }; using SubDirList = std::vector<std::string>; diff --git a/common/cpp/include/common/networking.h b/common/cpp/include/common/networking.h index 5295416185659c4f49f6f1e92a4d329fe32ae939..8e6dd304606621514ff029e93a0682f3dd7a7f7f 100644 --- a/common/cpp/include/common/networking.h +++ b/common/cpp/include/common/networking.h @@ -25,11 +25,10 @@ enum Opcode : uint8_t { enum NetworkErrorCode : uint16_t { kNetErrorNoError, + kNetErrorWarning, kNetErrorWrongRequest, kNetErrorNoData, kNetAuthorizationError, - kNetErrorFileIdAlreadyInUse, - kNetErrorAllocateStorageFailed, kNetErrorInternalServerError = 65535, }; diff --git a/common/cpp/include/database/database.h b/common/cpp/include/database/database.h index 8d0bca0973ea5f29fda851782bb17b18c781afe0..32fa67929c24156e34614c5da8ef8b7c80427d30 100644 --- a/common/cpp/include/database/database.h +++ b/common/cpp/include/database/database.h @@ -21,6 +21,9 @@ class Database { uint64_t subset_size, bool ignore_duplicates) const = 0; + virtual Error GetById(const std::string& collection, uint64_t id, FileInfo* file) const = 0; + virtual Error GetDataSetById(const std::string& collection, uint64_t set_id, uint64_t id, FileInfo* file) const = 0; + virtual ~Database() = default; }; diff --git a/common/cpp/include/database/db_error.h b/common/cpp/include/database/db_error.h index c36e52458ac127e484513428b63fbe4c5e863973..cb3090045ccab96c2002f557f068b7a9e502c4aa 100644 --- a/common/cpp/include/database/db_error.h +++ b/common/cpp/include/database/db_error.h @@ -15,7 +15,8 @@ enum class DBErrorType { kDuplicateID, kAlreadyConnected, kBadAddress, - kMemoryError + kMemoryError, + kNoRecord }; using DBError = ServiceError<DBErrorType, ErrorType::kDBError>; @@ -23,6 +24,11 @@ using DBErrorTemplate = ServiceErrorTemplate<DBErrorType, ErrorType::kDBError>; namespace DBErrorTemplates { +auto const kNoRecord = DBErrorTemplate { + "No record", DBErrorType::kNoRecord +}; + + auto const kNotConnected = DBErrorTemplate { "Not connected", DBErrorType::kNotConnected }; diff --git a/common/cpp/include/io/io.h b/common/cpp/include/io/io.h index 92e79f10e4c951dcd31923c4fe8f5c480481432c..b78016f401cbca063e79e00f3478927426065777 100644 --- a/common/cpp/include/io/io.h +++ b/common/cpp/include/io/io.h @@ -100,11 +100,11 @@ class IO { virtual size_t Write (FileDescriptor fd, const void* buf, size_t length, Error* err) const = 0; virtual Error RemoveFile(const std::string& fname) const = 0; virtual Error WriteDataToFile (const std::string& root_folder, const std::string& fname, const FileData& data, - size_t length, bool create_directories) const = 0; + size_t length, bool create_directories, bool allow_ovewrite) const = 0; virtual Error WriteDataToFile (const std::string& root_folder, const std::string& fname, const uint8_t* data, - size_t length, bool create_directories) const = 0; + size_t length, bool create_directories, bool allow_ovewrite) const = 0; virtual Error ReceiveDataToFile(SocketDescriptor socket, const std::string& root_folder, const std::string& fname, - size_t length, bool create_directories) const = 0; + size_t length, bool create_directories, bool allow_ovewrite) const = 0; virtual void CreateNewDirectory (const std::string& directory_name, Error* err) const = 0; virtual FileData GetDataFromFile (const std::string& fname, uint64_t* fsize, Error* err) const = 0; virtual SubDirList GetSubDirectories(const std::string& path, Error* err) const = 0; diff --git a/common/cpp/include/preprocessor/definitions.h b/common/cpp/include/preprocessor/definitions.h index 385ffd37242d3b209a7f95cc0a7404c0d7cd4fe9..3ac042caac7247b75863a6a3301dd780fe25bd00 100644 --- a/common/cpp/include/preprocessor/definitions.h +++ b/common/cpp/include/preprocessor/definitions.h @@ -3,8 +3,10 @@ #ifdef UNIT_TESTS #define VIRTUAL virtual +#define FINAL #else #define VIRTUAL +#define FINAL final #endif namespace asapo { diff --git a/common/cpp/include/request/request.h b/common/cpp/include/request/request.h index 8bb82cb83171d1b8b5db98dee5812a7f7f46edae..0dfb3e54848211a80498f064264d854ff5f4723d 100644 --- a/common/cpp/include/request/request.h +++ b/common/cpp/include/request/request.h @@ -1,6 +1,8 @@ #ifndef ASAPO_GENERIC_REQUEST_H #define ASAPO_GENERIC_REQUEST_H +#include <chrono> + #include "common/networking.h" #include "common/data_structs.h" #include "io/io.h" @@ -11,9 +13,28 @@ namespace asapo { class GenericRequest { public: GenericRequest() = delete; - GenericRequest(GenericRequestHeader h): header{std::move(h)} {}; + GenericRequest(GenericRequestHeader h, uint64_t timeout_ms): header{std::move(h)}, timeout_ms_{timeout_ms} {}; GenericRequestHeader header; virtual ~GenericRequest() = default; + uint64_t GetRetryCounter() { + return retry_counter_; + } + void IncreaseRetryCounter() { + retry_counter_++; + } + bool TimedOut() { + if (timeout_ms_ == 0) { + return false; + } + uint64_t elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - + created_at_).count(); + return elapsed_ms > timeout_ms_; + } + + private: + uint64_t retry_counter_ = 0; + uint64_t timeout_ms_ = 0; + std::chrono::system_clock::time_point created_at_ = std::chrono::system_clock::now(); }; using GenericRequestPtr = std::unique_ptr<GenericRequest>; diff --git a/common/cpp/include/request/request_handler.h b/common/cpp/include/request/request_handler.h index 25f63d653abb7bc63a659b11c84df4353218165b..60fbd767baf396e7036bd2c430d188164324773a 100644 --- a/common/cpp/include/request/request_handler.h +++ b/common/cpp/include/request/request_handler.h @@ -14,6 +14,7 @@ class RequestHandler { virtual void PrepareProcessingRequestLocked() = 0; virtual void TearDownProcessingRequestLocked(bool processing_succeeded) = 0; virtual bool ProcessRequestUnlocked(GenericRequest* request) = 0; + virtual void ProcessRequestTimeout(GenericRequest* request) = 0; virtual bool ReadyProcessRequest() = 0; virtual ~RequestHandler() = default; }; diff --git a/common/cpp/include/unittests/MockDatabase.h b/common/cpp/include/unittests/MockDatabase.h index 85ff272d882e5d94468bba643628f9b2767ccd76..b1ac72561ec8b715297f80c1ca4be39474068692 100644 --- a/common/cpp/include/unittests/MockDatabase.h +++ b/common/cpp/include/unittests/MockDatabase.h @@ -38,7 +38,18 @@ class MockDatabase : public Database { } MOCK_CONST_METHOD4(Upsert_t, ErrorInterface * (const std::string&, uint64_t id, const uint8_t* data, uint64_t size)); + Error GetById(const std::string& collection, uint64_t id, FileInfo* file) const override { + return Error{GetById_t(collection, id, file)}; + } + + MOCK_CONST_METHOD3(GetById_t, ErrorInterface * (const std::string&, uint64_t id, FileInfo*)); + + + Error GetDataSetById(const std::string& collection, uint64_t set_id, uint64_t id, FileInfo* file) const override { + return Error{GetSetById_t(collection, set_id, id, file)}; + } + MOCK_CONST_METHOD4(GetSetById_t, ErrorInterface * (const std::string&, uint64_t set_id, uint64_t id, FileInfo*)); // stuff to test db destructor is called and avoid "uninteresting call" messages MOCK_METHOD0(Die, void()); diff --git a/common/cpp/include/unittests/MockIO.h b/common/cpp/include/unittests/MockIO.h index e24eee23dff64aa04b8d26be397fb79ff81e38fb..c5701792106f447dff4c8658f4f2494864e0c7e8 100644 --- a/common/cpp/include/unittests/MockIO.h +++ b/common/cpp/include/unittests/MockIO.h @@ -217,16 +217,16 @@ class MockIO : public IO { MOCK_CONST_METHOD3(SendFile_t, ErrorInterface * (SocketDescriptor socket_fd, const std::string& fname, size_t length)); Error WriteDataToFile(const std::string& root_folder, const std::string& fname, const FileData& data, - size_t length, bool create_directories) const override { - return Error{WriteDataToFile_t(root_folder, fname, data.get(), length, create_directories)}; + size_t length, bool create_directories, bool allow_ovewrite) const override { + return Error{WriteDataToFile_t(root_folder, fname, data.get(), length, create_directories, allow_ovewrite)}; } MOCK_CONST_METHOD1(RemoveFile_t, ErrorInterface * (const std::string& fname)); Error WriteDataToFile(const std::string& root_folder, const std::string& fname, const uint8_t* data, - size_t length, bool create_directories) const override { - return Error{WriteDataToFile_t(root_folder, fname, data, length, create_directories)}; + size_t length, bool create_directories, bool allow_ovewrite) const override { + return Error{WriteDataToFile_t(root_folder, fname, data, length, create_directories, allow_ovewrite)}; } @@ -235,17 +235,17 @@ class MockIO : public IO { } - MOCK_CONST_METHOD5(ReceiveDataToFile_t, ErrorInterface * (SocketDescriptor socket, const std::string& root_folder, - const std::string& fname, size_t fsize, bool create_directories)); + MOCK_CONST_METHOD6(ReceiveDataToFile_t, ErrorInterface * (SocketDescriptor socket, const std::string& root_folder, + const std::string& fname, size_t fsize, bool create_directories, bool allow_ovewrite)); Error ReceiveDataToFile(SocketDescriptor socket, const std::string& root_folder, const std::string& fname, - size_t length, bool create_directories) const override { - return Error{ReceiveDataToFile_t(socket, root_folder, fname, length, create_directories)}; + size_t length, bool create_directories, bool allow_ovewrite) const override { + return Error{ReceiveDataToFile_t(socket, root_folder, fname, length, create_directories, allow_ovewrite)}; } - MOCK_CONST_METHOD5(WriteDataToFile_t, ErrorInterface * (const std::string& root_folder, const std::string& fname, - const uint8_t* data, size_t fsize, bool create_directories)); + MOCK_CONST_METHOD6(WriteDataToFile_t, ErrorInterface * (const std::string& root_folder, const std::string& fname, + const uint8_t* data, size_t fsize, bool create_directories, bool allow_ovewrite)); FileInfo GetFileInfo(const std::string& name, Error* err) const override { diff --git a/common/cpp/src/data_structs/data_structs.cpp b/common/cpp/src/data_structs/data_structs.cpp index 0c87a7f28bd7f6cce1d6ac076232c68c280f92f7..bdda7fa5f6f45dc79c59c4deea0eb9e676da3f76 100644 --- a/common/cpp/src/data_structs/data_structs.cpp +++ b/common/cpp/src/data_structs/data_structs.cpp @@ -64,7 +64,29 @@ bool TimeFromJson(const JsonStringParser& parser, const std::string name, std::c return true; } +bool DataSet::SetFromJson(const std::string& json_string) { + auto old = *this; + + auto parser = JsonStringParser(std::move(json_string)); + std::vector<std::string> vec_fi_endcoded; + Error parse_err; + (parse_err = parser.GetArrayRawStrings("images", &vec_fi_endcoded)) || + (parse_err = parser.GetUInt64("_id", &id)); + if (parse_err) { + *this = old; + return false; + } + for (auto fi_encoded : vec_fi_endcoded) { + FileInfo fi; + if (!fi.SetFromJson(fi_encoded)) { + *this = old; + return false; + } + content.emplace_back(fi); + } + return true; +} bool FileInfo::SetFromJson(const std::string& json_string) { auto old = *this; diff --git a/common/cpp/src/database/mongodb_client.cpp b/common/cpp/src/database/mongodb_client.cpp index 9380bdcd086e7d40e94dd768fe46fc5534da75e1..0330c14df2bfdd893b8adac710bde98d6ece407f 100644 --- a/common/cpp/src/database/mongodb_client.cpp +++ b/common/cpp/src/database/mongodb_client.cpp @@ -1,9 +1,9 @@ #include "mongodb_client.h" +#include "mongodb_client.h" #include "database/db_error.h" namespace asapo { -using std::string; using asapo::Database; MongoDbInstance::MongoDbInstance() { @@ -33,7 +33,7 @@ MongoDBClient::MongoDBClient() { MongoDbInstance::Instantiate(); } -Error MongoDBClient::InitializeClient(const string& address) { +Error MongoDBClient::InitializeClient(const std::string& address) { auto uri_str = DBAddress(address); client_ = mongoc_client_new (uri_str.c_str()); @@ -49,7 +49,7 @@ Error MongoDBClient::InitializeClient(const string& address) { } -void MongoDBClient::UpdateCurrentCollectionIfNeeded(const string& collection_name) const { +void MongoDBClient::UpdateCurrentCollectionIfNeeded(const std::string& collection_name) const { if (collection_name == current_collection_name_) { return; } @@ -71,7 +71,7 @@ Error MongoDBClient::TryConnectDatabase() { return err; } -Error MongoDBClient::Connect(const string& address, const string& database_name) { +Error MongoDBClient::Connect(const std::string& address, const std::string& database_name) { if (connected_) { return DBErrorTemplates::kAlreadyConnected.Generate(); } @@ -90,7 +90,7 @@ Error MongoDBClient::Connect(const string& address, const string& database_name) return err; } -string MongoDBClient::DBAddress(const string& address) const { +std::string MongoDBClient::DBAddress(const std::string& address) const { return "mongodb://" + address + "/?appname=asapo"; } @@ -271,4 +271,85 @@ Error MongoDBClient::InsertAsSubset(const std::string& collection, const FileInf return err; } +Error MongoDBClient::GetRecordFromDb(const std::string& collection, uint64_t id, std::string* res) const { + if (!connected_) { + return DBErrorTemplates::kNotConnected.Generate(); + } + + UpdateCurrentCollectionIfNeeded(collection); + + + Error err; + bson_error_t mongo_err; + bson_t* filter; + bson_t* opts; + mongoc_cursor_t* cursor; + const bson_t* doc; + char* str; + + filter = BCON_NEW ("_id", BCON_INT64 (id)); + opts = BCON_NEW ("limit", BCON_INT64 (1)); + + cursor = mongoc_collection_find_with_opts (current_collection_, filter, opts, NULL); + + bool found = false; + while (mongoc_cursor_next (cursor, &doc)) { + str = bson_as_relaxed_extended_json (doc, NULL); + *res = str; + found = true; + bson_free (str); + } + + if (mongoc_cursor_error (cursor, &mongo_err)) { + err = DBErrorTemplates::kDBError.Generate(mongo_err.message); + } else { + if (!found) { + err = DBErrorTemplates::kNoRecord.Generate(); + } + } + + mongoc_cursor_destroy (cursor); + bson_destroy (filter); + bson_destroy (opts); + + return err; +} + + +Error MongoDBClient::GetById(const std::string& collection, uint64_t id, FileInfo* file) const { + std::string record_str; + auto err = GetRecordFromDb(collection, id, &record_str); + if (err) { + return err; + } + + if (!file->SetFromJson(record_str)) { + DBErrorTemplates::kJsonParseError.Generate(record_str); + } + return nullptr; +} + +Error MongoDBClient::GetDataSetById(const std::string& collection, uint64_t set_id, uint64_t id, FileInfo* file) const { + std::string record_str; + auto err = GetRecordFromDb(collection, set_id, &record_str); + if (err) { + return err; + } + + DataSet dataset; + if (!dataset.SetFromJson(record_str)) { + DBErrorTemplates::kJsonParseError.Generate(record_str); + } + + for (const auto& fileinfo : dataset.content) { + if (fileinfo.id == id) { + *file = fileinfo; + return nullptr; + } + } + + return DBErrorTemplates::kNoRecord.Generate(); + +} + } diff --git a/common/cpp/src/database/mongodb_client.h b/common/cpp/src/database/mongodb_client.h index 0544d79122fb1d59d3126ed24e69d9675317eab1..75303a459761908cb8e76e55075c12322919c5bd 100644 --- a/common/cpp/src/database/mongodb_client.h +++ b/common/cpp/src/database/mongodb_client.h @@ -41,6 +41,8 @@ class MongoDBClient final : public Database { Error InsertAsSubset(const std::string& collection, const FileInfo& file, uint64_t subset_id, uint64_t subset_size, bool ignore_duplicates) const override; Error Upsert(const std::string& collection, uint64_t id, const uint8_t* data, uint64_t size) const override; + Error GetById(const std::string& collection, uint64_t id, FileInfo* file) const override; + Error GetDataSetById(const std::string& collection, uint64_t set_id, uint64_t id, FileInfo* file) const override; ~MongoDBClient() override; private: mongoc_client_t* client_{nullptr}; @@ -58,6 +60,8 @@ class MongoDBClient final : public Database { Error InsertBsonDocument(const bson_p& document, bool ignore_duplicates) const; Error UpdateBsonDocument(uint64_t id, const bson_p& document, bool upsert) const; Error AddBsonDocumentToArray(bson_t* query, bson_t* update, bool ignore_duplicates) const; + Error GetRecordFromDb(const std::string& collection, uint64_t id, std::string* res) const; + }; } diff --git a/common/cpp/src/request/request_pool.cpp b/common/cpp/src/request/request_pool.cpp index ccf724e367b7c3344c3efc8253f881b89223b73c..de91b3f1501b6b42507df7318c71e7187a3e629b 100644 --- a/common/cpp/src/request/request_pool.cpp +++ b/common/cpp/src/request/request_pool.cpp @@ -35,13 +35,19 @@ GenericRequestPtr RequestPool::GetRequestFromQueue() { } void RequestPool::PutRequestBackToQueue(GenericRequestPtr request) { +// do not need to lock since we already own it + request->IncreaseRetryCounter(); request_queue_.emplace_front(std::move(request)); } void RequestPool::ProcessRequest(const std::unique_ptr<RequestHandler>& request_handler, ThreadInformation* thread_info) { - request_handler->PrepareProcessingRequestLocked(); auto request = GetRequestFromQueue(); + if (request->TimedOut()) { + request_handler->ProcessRequestTimeout(request.get()); + return; + } + request_handler->PrepareProcessingRequestLocked(); requests_in_progress_++; thread_info->lock.unlock(); auto success = request_handler->ProcessRequestUnlocked(request.get()); @@ -50,8 +56,8 @@ void RequestPool::ProcessRequest(const std::unique_ptr<RequestHandler>& request_ request_handler->TearDownProcessingRequestLocked(success); if (!success) { PutRequestBackToQueue(std::move(request)); - condition_.notify_all(); thread_info->lock.unlock(); + condition_.notify_all(); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); thread_info->lock.lock(); } diff --git a/common/cpp/src/system_io/system_io.cpp b/common/cpp/src/system_io/system_io.cpp index 6bc1e55dd17e0f7344232651303a5a73f020aac8..e3c8b5dc94a667f1e5734d9c23d92176e72499be 100644 --- a/common/cpp/src/system_io/system_io.cpp +++ b/common/cpp/src/system_io/system_io.cpp @@ -147,14 +147,15 @@ void asapo::SystemIO::CreateNewDirectory(const std::string& directory_name, Erro } FileDescriptor SystemIO::OpenWithCreateFolders(const std::string& root_folder, const std::string& fname, - bool create_directories, Error* err) const { + bool create_directories, bool allow_ovewrite, Error* err) const { std::string full_name; if (!root_folder.empty()) { full_name = root_folder + kPathSeparator + fname; } else { full_name = fname; } - auto fd = Open(full_name, IO_OPEN_MODE_CREATE | IO_OPEN_MODE_RW | IO_OPEN_MODE_SET_LENGTH_0, err); + auto create_flag = allow_ovewrite ? IO_OPEN_MODE_CREATE : IO_OPEN_MODE_CREATE_AND_FAIL_IF_EXISTS; + auto fd = Open(full_name, create_flag | IO_OPEN_MODE_RW | IO_OPEN_MODE_SET_LENGTH_0, err); if (*err == IOErrorTemplates::kFileNotFound && create_directories) { size_t pos = fname.rfind(kPathSeparator); if (pos == std::string::npos) { @@ -165,7 +166,7 @@ FileDescriptor SystemIO::OpenWithCreateFolders(const std::string& root_folder, c if (*err) { return -1; } - return OpenWithCreateFolders(root_folder, fname, false, err); + return OpenWithCreateFolders(root_folder, fname, false, allow_ovewrite, err); } return fd; @@ -173,9 +174,9 @@ FileDescriptor SystemIO::OpenWithCreateFolders(const std::string& root_folder, c } Error SystemIO::WriteDataToFile(const std::string& root_folder, const std::string& fname, const uint8_t* data, - size_t length, bool create_directories) const { + size_t length, bool create_directories, bool allow_ovewrite) const { Error err; - auto fd = OpenWithCreateFolders(root_folder, fname, create_directories, &err); + auto fd = OpenWithCreateFolders(root_folder, fname, create_directories, allow_ovewrite, &err); if (err) { return err; } @@ -192,8 +193,8 @@ Error SystemIO::WriteDataToFile(const std::string& root_folder, const std::strin } Error SystemIO::WriteDataToFile(const std::string& root_folder, const std::string& fname, const FileData& data, - size_t length, bool create_directories) const { - return WriteDataToFile(root_folder, fname, data.get(), length, create_directories); + size_t length, bool create_directories, bool allow_ovewrite) const { + return WriteDataToFile(root_folder, fname, data.get(), length, create_directories, allow_ovewrite); } std::string SystemIO::ReadFileToString(const std::string& fname, Error* err) const { @@ -663,9 +664,9 @@ Error SystemIO::SendFile(SocketDescriptor socket_fd, const std::string& fname, s } Error SystemIO:: ReceiveDataToFile(SocketDescriptor socket, const std::string& root_folder, const std::string& fname, - size_t length, bool create_directories) const { + size_t length, bool create_directories, bool allow_ovewrite) const { Error err; - auto fd = OpenWithCreateFolders(root_folder, fname, create_directories, &err); + auto fd = OpenWithCreateFolders(root_folder, fname, create_directories, allow_ovewrite, &err); if (err) { return err; } diff --git a/common/cpp/src/system_io/system_io.h b/common/cpp/src/system_io/system_io.h index 69443af6b907eaeb96b8c23dce4c22c41436c11b..78621e1fce011efa0741a7681dabe0e59793d42c 100644 --- a/common/cpp/src/system_io/system_io.h +++ b/common/cpp/src/system_io/system_io.h @@ -84,7 +84,7 @@ class SystemIO final : public IO { Error CreateDirectoryWithParents(const std::string& root_path, const std::string& path) const; uint8_t* AllocateArray(uint64_t fsize, Error* err) const; FileDescriptor OpenWithCreateFolders(const std::string& root_folder, const std::string& fname, - bool create_directories, Error* err) const; + bool create_directories, bool allow_ovewrite, Error* err) const; public: ~SystemIO(); /* @@ -133,12 +133,12 @@ class SystemIO final : public IO { void CreateNewDirectory(const std::string& directory_name, Error* err) const override; FileData GetDataFromFile(const std::string& fname, uint64_t* fsize, Error* err) const override; Error WriteDataToFile (const std::string& root_folder, const std::string& fname, const FileData& data, - size_t length, bool create_directories) const override; + size_t length, bool create_directories, bool allow_ovewrite) const override; Error ReceiveDataToFile(SocketDescriptor socket, const std::string& root_folder, const std::string& fname, - size_t length, bool create_directories) const override; + size_t length, bool create_directories, bool allow_ovewrite) const override; Error WriteDataToFile(const std::string& root_folder, const std::string& fname, const uint8_t* data, - size_t length, bool create_directories) const override; + size_t length, bool create_directories, bool allow_ovewrite) const override; SubDirList GetSubDirectories(const std::string& path, Error* err) const override; std::string ReadFileToString(const std::string& fname, Error* err) const override; Error RemoveFile(const std::string& fname) const override; diff --git a/common/cpp/unittests/request/mocking.h b/common/cpp/unittests/request/mocking.h index 9e9ef6d5c13fc5b32fda21a53d8b22b95e8af306..d37fdd79d7ff50b58597f814b35307cc56acba48 100644 --- a/common/cpp/unittests/request/mocking.h +++ b/common/cpp/unittests/request/mocking.h @@ -16,8 +16,10 @@ class MockRequestHandler : public RequestHandler { MOCK_METHOD0(ReadyProcessRequest, bool()); MOCK_METHOD1(TearDownProcessingRequestLocked, void(bool processing_succeeded)); MOCK_METHOD1(ProcessRequestUnlocked_t, bool (const GenericRequest* request)); - + MOCK_METHOD1(ProcessRequestTimeout, void(GenericRequest* request)); + uint64_t retry_counter = 0; bool ProcessRequestUnlocked(GenericRequest* request) override { + retry_counter = request->GetRetryCounter(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); return ProcessRequestUnlocked_t(request); } diff --git a/common/cpp/unittests/request/test_request.cpp b/common/cpp/unittests/request/test_request.cpp index 80669cddb9de96f7cca8eb3f551ea24aafef5586..c632486ee3bc6053e7d7826f0c9c51ece9c8e61d 100644 --- a/common/cpp/unittests/request/test_request.cpp +++ b/common/cpp/unittests/request/test_request.cpp @@ -37,15 +37,27 @@ using asapo::GenericRequest; using asapo::GenericRequestHeader; -TEST(Request, Constructor) { +TEST(Request, Tests) { GenericRequestHeader header{asapo::kOpcodeTransferData, 1, 2, 3, "hello"}; - GenericRequest r{header}; + GenericRequest r{header, 90}; ASSERT_THAT(r.header.data_id, Eq(1)); ASSERT_THAT(r.header.op_code, Eq(asapo::kOpcodeTransferData)); ASSERT_THAT(r.header.data_size, Eq(2)); ASSERT_THAT(r.header.meta_size, Eq(3)); ASSERT_THAT(r.header.message, testing::StrEq("hello")); + ASSERT_THAT(r.GetRetryCounter(), Eq(0)); + ASSERT_THAT(r.TimedOut(), Eq(false)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + ASSERT_THAT(r.TimedOut(), Eq(true)); + r.IncreaseRetryCounter(); + ASSERT_THAT(r.GetRetryCounter(), Eq(1)); + + GenericRequest r_notimeout{header, 0}; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ASSERT_THAT(r_notimeout.TimedOut(), Eq(false)); + + } } diff --git a/common/cpp/unittests/request/test_request_pool.cpp b/common/cpp/unittests/request/test_request_pool.cpp index 91c5cb790ce5c392dc3df1797999c21eb83be0f5..447d4c0e9c78206d631621d016b63e8990de1f9f 100644 --- a/common/cpp/unittests/request/test_request_pool.cpp +++ b/common/cpp/unittests/request/test_request_pool.cpp @@ -53,7 +53,7 @@ class MockRequestHandlerFactory : public asapo::RequestHandlerFactory { class TestRequest : public GenericRequest { public: - TestRequest(GenericRequestHeader header): GenericRequest(header) {}; + TestRequest(GenericRequestHeader header, uint64_t timeout): GenericRequest(header, timeout) {}; }; @@ -64,7 +64,7 @@ class RequestPoolTests : public testing::Test { MockRequestHandlerFactory request_handler_factory{mock_request_handler}; const uint8_t nthreads = 1; asapo::RequestPool pool {nthreads, &request_handler_factory, &mock_logger}; - std::unique_ptr<GenericRequest> request{new TestRequest{GenericRequestHeader{}}}; + std::unique_ptr<GenericRequest> request{new TestRequest{GenericRequestHeader{}, 0}}; void SetUp() override { } void TearDown() override { @@ -98,7 +98,20 @@ TEST_F(RequestPoolTests, NRequestsInPoolInitial) { ASSERT_THAT(nreq, Eq(0)); } +TEST_F(RequestPoolTests, TimeOut) { + std::unique_ptr<GenericRequest> request{new TestRequest{GenericRequestHeader{}, 10}}; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + EXPECT_CALL(*mock_request_handler, ReadyProcessRequest()).Times(1).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_request_handler, PrepareProcessingRequestLocked()).Times(0); + EXPECT_CALL(*mock_request_handler, ProcessRequestUnlocked_t(_)).Times(0); + EXPECT_CALL(*mock_request_handler, ProcessRequestTimeout(_)).Times(1); + + auto err = pool.AddRequest(std::move(request)); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + ASSERT_THAT(err, Eq(nullptr)); +} void ExpectSend(MockRequestHandler* mock_handler, int ntimes = 1) { EXPECT_CALL(*mock_handler, ReadyProcessRequest()).Times(ntimes).WillRepeatedly(Return(true)); @@ -107,6 +120,26 @@ void ExpectSend(MockRequestHandler* mock_handler, int ntimes = 1) { EXPECT_CALL(*mock_handler, TearDownProcessingRequestLocked(true)).Times(ntimes); } +void ExpectFailProcessRequest(MockRequestHandler* mock_handler) { + EXPECT_CALL(*mock_handler, ReadyProcessRequest()).Times(AtLeast(1)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_handler, PrepareProcessingRequestLocked()).Times(AtLeast(1)); + EXPECT_CALL(*mock_handler, ProcessRequestUnlocked_t(_)).Times(AtLeast(1)).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_handler, TearDownProcessingRequestLocked(false)).Times(AtLeast(1)); +} + + + +TEST_F(RequestPoolTests, AddRequestIncreasesRetryCounter) { + + ExpectFailProcessRequest(mock_request_handler); + + auto err = pool.AddRequest(std::move(request)); + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + ASSERT_THAT(err, Eq(nullptr)); + ASSERT_THAT(mock_request_handler->retry_counter, Gt(0)); +} + TEST_F(RequestPoolTests, AddRequestCallsSend) { @@ -145,7 +178,7 @@ TEST_F(RequestPoolTests, NRequestsInPoolAccountsForRequestsInProgress) { TEST_F(RequestPoolTests, AddRequestCallsSendTwoRequests) { - TestRequest* request2 = new TestRequest{GenericRequestHeader{}}; + TestRequest* request2 = new TestRequest{GenericRequestHeader{}, 0}; ExpectSend(mock_request_handler, 2); @@ -161,7 +194,7 @@ TEST_F(RequestPoolTests, AddRequestCallsSendTwoRequests) { TEST_F(RequestPoolTests, AddRequestsOk) { - TestRequest* request2 = new TestRequest{GenericRequestHeader{}}; + TestRequest* request2 = new TestRequest{GenericRequestHeader{}, 0}; ExpectSend(mock_request_handler, 2); diff --git a/consumer/api/cpp/src/server_data_broker.cpp b/consumer/api/cpp/src/server_data_broker.cpp index 685aafb1010dd9aff22fb265cb8442193b69f241..4078698d39146906774f575d26508da0446db2f2 100644 --- a/consumer/api/cpp/src/server_data_broker.cpp +++ b/consumer/api/cpp/src/server_data_broker.cpp @@ -395,28 +395,14 @@ std::string ServerDataBroker::GetBeamtimeMeta(Error* err) { } DataSet ServerDataBroker::DecodeDatasetFromResponse(std::string response, Error* err) { - auto parser = JsonStringParser(std::move(response)); - - std::vector<std::string> vec_fi_endcoded; - Error parse_err; - uint64_t id; - (parse_err = parser.GetArrayRawStrings("images", &vec_fi_endcoded)) || - (parse_err = parser.GetUInt64("_id", &id)); - if (parse_err) { - *err = ConsumerErrorTemplates::kInterruptedTransaction.Generate("malformed response:" + parse_err->Explain()); + DataSet res; + if (!res.SetFromJson(std::move(response))) { + *err = ConsumerErrorTemplates::kInterruptedTransaction.Generate("malformed response:" + response); return {0, FileInfos{}}; + } else { + *err = nullptr; + return res; } - - auto res = FileInfos{}; - for (auto fi_encoded : vec_fi_endcoded) { - FileInfo fi; - if (!fi.SetFromJson(fi_encoded)) { - *err = ConsumerErrorTemplates::kInterruptedTransaction.Generate("malformed response:" + fi_encoded); - return {0, FileInfos{}}; - } - res.emplace_back(fi); - } - return {id, std::move(res)}; } FileInfos ServerDataBroker::QueryImages(std::string query, std::string substream, Error* err) { diff --git a/deploy/build_env/Ubuntu16.04/install_cmake.sh b/deploy/build_env/Ubuntu16.04/install_cmake.sh index 55245ffb3a58f94111e147c138a79a488279c6ca..3e399d4ec40810521665eb8a1b1a678b73e4af08 100755 --- a/deploy/build_env/Ubuntu16.04/install_cmake.sh +++ b/deploy/build_env/Ubuntu16.04/install_cmake.sh @@ -8,6 +8,9 @@ cd cmake-3.* make -j$(nproc) make install +cd .. +rm -rf cmake-3* + cmake --version /usr/local/bin/cmake --version diff --git a/docs/doxygen/main/DoxygenLayout.xml b/docs/doxygen/main/DoxygenLayout.xml index fc5fb0847a61bc0ed0b791bd236c757a5f6c26fc..702bee96b4e3c14080ba20ac35fc205b80597033 100644 --- a/docs/doxygen/main/DoxygenLayout.xml +++ b/docs/doxygen/main/DoxygenLayout.xml @@ -1,4 +1,4 @@ <navindex> - <tab type="user" url="consumer/index.html" title="Consumer API"/> - <tab type="user" url="producer/index.html" title="Producer API"/> + <tab type="user" url="consumer/index.html" visible="$ALPHABETICAL_INDEX" title="Consumer API"/> + <tab type="user" url="producer/index.html" visible="$ALPHABETICAL_INDEX" title="Producer API"/> </navindex> diff --git a/examples/pipeline/in_to_out/in_to_out.cpp b/examples/pipeline/in_to_out/in_to_out.cpp index 11ff051b1dd9d3d75122afa16e3b09503d8cdcaa..714e704c2f9e2406ab67166b02c7614f9a52d68d 100644 --- a/examples/pipeline/in_to_out/in_to_out.cpp +++ b/examples/pipeline/in_to_out/in_to_out.cpp @@ -39,7 +39,7 @@ struct Args { }; void ProcessAfterSend(asapo::GenericRequestHeader header, asapo::Error err) { - if (err) { + if (err && err!=asapo::ProducerErrorTemplates::kServerWarning) { std::cerr << "Data was not successfully send: " << err << std::endl; return; } @@ -188,7 +188,7 @@ std::unique_ptr<asapo::Producer> CreateProducer(const Args& args) { asapo::Error err; auto producer = asapo::Producer::Create(args.server, args.nthreads, asapo::RequestHandlerType::kTcp, - asapo::SourceCredentials{args.beamtime_id, args.stream_out, args.token }, &err); + asapo::SourceCredentials{args.beamtime_id, args.stream_out, args.token }, 60, &err); if(err) { std::cerr << "Cannot start producer. ProducerError: " << err << std::endl; exit(EXIT_FAILURE); diff --git a/examples/pipeline/in_to_out_python/in_to_out.py b/examples/pipeline/in_to_out_python/in_to_out.py index b8a51975c9849877aaa597abe41956e8e5c14bfc..4c67b594795c3969babb0e937842088a5f1a9303 100644 --- a/examples/pipeline/in_to_out_python/in_to_out.py +++ b/examples/pipeline/in_to_out_python/in_to_out.py @@ -30,7 +30,7 @@ transfer_data=int(transfer_data)>0 broker = asapo_consumer.create_server_broker(source,path, beamtime,stream_in,token,timeout_s*1000) -producer = asapo_producer.create_producer(source,beamtime, stream_out, token, nthreads) +producer = asapo_producer.create_producer(source,beamtime, stream_out, token, nthreads, 600) group_id = broker.generate_group_id() diff --git a/examples/producer/dummy-data-producer/dummy_data_producer.cpp b/examples/producer/dummy-data-producer/dummy_data_producer.cpp index e2b0343859b676244ad6924b29f3d3b7cc765ef6..9a7be1a9c0fed9041ecbd20e4b675433e916f8ac 100644 --- a/examples/producer/dummy-data-producer/dummy_data_producer.cpp +++ b/examples/producer/dummy-data-producer/dummy_data_producer.cpp @@ -180,7 +180,7 @@ std::unique_ptr<asapo::Producer> CreateProducer(const Args& args) { asapo::Error err; auto producer = asapo::Producer::Create(args.receiver_address, args.nthreads, args.mode % 10 == 0 ? asapo::RequestHandlerType::kTcp : asapo::RequestHandlerType::kFilesystem, - asapo::SourceCredentials{args.beamtime_id, args.stream, args.token }, &err); + asapo::SourceCredentials{args.beamtime_id, args.stream, args.token }, 3600, &err); if(err) { std::cerr << "Cannot start producer. ProducerError: " << err << std::endl; exit(EXIT_FAILURE); diff --git a/producer/api/cpp/include/producer/producer.h b/producer/api/cpp/include/producer/producer.h index 3dc1def79d5d59a1dd3e22a560f8e02bbb725ca7..f4e4b43c5dbdd085af87c162b27b93fed584790e 100644 --- a/producer/api/cpp/include/producer/producer.h +++ b/producer/api/cpp/include/producer/producer.h @@ -20,6 +20,7 @@ class Producer { */ static std::unique_ptr<Producer> Create(const std::string& endpoint, uint8_t n_processing_threads, asapo::RequestHandlerType type, SourceCredentials source_cred, + uint64_t timeout_sec, Error* err); virtual ~Producer() = default; diff --git a/producer/api/cpp/include/producer/producer_error.h b/producer/api/cpp/include/producer/producer_error.h index 0dc331258da6d07b3d42a73bba22ba74e29659d4..077c4108be588e6dd8c9de903a2d7c2210e0fc8d 100644 --- a/producer/api/cpp/include/producer/producer_error.h +++ b/producer/api/cpp/include/producer/producer_error.h @@ -10,6 +10,7 @@ enum class ProducerErrorType { kRequestPoolIsFull, kLocalIOError, kWrongInput, + kServerWarning, kTimeout }; @@ -17,6 +18,10 @@ using ProducerErrorTemplate = ServiceErrorTemplate<ProducerErrorType, ErrorType: namespace ProducerErrorTemplates { +auto const kServerWarning = ProducerErrorTemplate { + "server warning", ProducerErrorType::kServerWarning +}; + auto const kLocalIOError = ProducerErrorTemplate { "local i/o error", ProducerErrorType::kLocalIOError }; diff --git a/producer/api/cpp/src/producer.cpp b/producer/api/cpp/src/producer.cpp index 84191fcf8b0e2a81c70574e612960f16b9a5cb58..d56b8cab8bbbbfb17c10d71951389ce35f72db79 100644 --- a/producer/api/cpp/src/producer.cpp +++ b/producer/api/cpp/src/producer.cpp @@ -3,7 +3,7 @@ #include "producer/producer_error.h" std::unique_ptr<asapo::Producer> asapo::Producer::Create(const std::string& endpoint, uint8_t n_processing_threads, - asapo::RequestHandlerType type, SourceCredentials source_cred, Error* err) { + asapo::RequestHandlerType type, SourceCredentials source_cred, uint64_t timeout_sec, Error* err) { if (n_processing_threads > kMaxProcessingThreads || n_processing_threads == 0) { *err = ProducerErrorTemplates::kWrongInput.Generate("Set number of processing threads > 0 and <= " + std::to_string( @@ -13,7 +13,7 @@ std::unique_ptr<asapo::Producer> asapo::Producer::Create(const std::string& endp std::unique_ptr<asapo::Producer> producer; try { - producer.reset(new ProducerImpl(endpoint, n_processing_threads, type)); + producer.reset(new ProducerImpl(endpoint, n_processing_threads, timeout_sec, type)); } catch (const std::exception& ex) { *err = TextError(ex.what()); return nullptr; diff --git a/producer/api/cpp/src/producer_impl.cpp b/producer/api/cpp/src/producer_impl.cpp index 12c3b03351abef6692466f734270c405fc2eecbd..a9a1e40e86e159db8764f101a001054904458eb8 100644 --- a/producer/api/cpp/src/producer_impl.cpp +++ b/producer/api/cpp/src/producer_impl.cpp @@ -18,8 +18,9 @@ const std::string ProducerImpl::kFinishSubStreamKeyword = "asapo_finish_substrea const std::string ProducerImpl::kNoNextSubStreamKeyword = "asapo_no_next"; -ProducerImpl::ProducerImpl(std::string endpoint, uint8_t n_processing_threads, asapo::RequestHandlerType type): - log__{GetDefaultProducerLogger()} { +ProducerImpl::ProducerImpl(std::string endpoint, uint8_t n_processing_threads, uint64_t timeout_sec, + asapo::RequestHandlerType type): + log__{GetDefaultProducerLogger()}, timeout_sec_{timeout_sec} { switch (type) { case RequestHandlerType::kTcp: discovery_service_.reset(new ReceiverDiscoveryService{endpoint, ProducerImpl::kDiscoveryServiceUpdateFrequencyMs}); @@ -91,7 +92,7 @@ Error ProducerImpl::Send(const EventHeader& event_header, auto request_header = GenerateNextSendRequest(event_header, std::move(substream), ingest_mode); return request_pool__->AddRequest(std::unique_ptr<ProducerRequest> {new ProducerRequest{source_cred_string_, std::move(request_header), - std::move(data), std::move(event_header.user_metadata), std::move(full_path), callback, manage_data_memory} + std::move(data), std::move(event_header.user_metadata), std::move(full_path), callback, manage_data_memory, timeout_sec_ * 1000} }); } @@ -187,7 +188,7 @@ Error ProducerImpl::SendMetaData(const std::string& metadata, RequestCallback ca FileData data{new uint8_t[metadata.size()]}; strncpy((char*)data.get(), metadata.c_str(), metadata.size()); return request_pool__->AddRequest(std::unique_ptr<ProducerRequest> {new ProducerRequest{source_cred_string_, std::move(request_header), - std::move(data), "", "", callback, true} + std::move(data), "", "", callback, true, timeout_sec_} }); } diff --git a/producer/api/cpp/src/producer_impl.h b/producer/api/cpp/src/producer_impl.h index c54644bdb7cabdfeebbe86861719201da7940429..371277f97d3f22d5ed6590effac664a0e9c74daf 100644 --- a/producer/api/cpp/src/producer_impl.h +++ b/producer/api/cpp/src/producer_impl.h @@ -22,7 +22,8 @@ class ProducerImpl : public Producer { static const std::string kFinishSubStreamKeyword; static const std::string kNoNextSubStreamKeyword; - explicit ProducerImpl(std::string endpoint, uint8_t n_processing_threads, asapo::RequestHandlerType type); + explicit ProducerImpl(std::string endpoint, uint8_t n_processing_threads, uint64_t timeout_sec, + asapo::RequestHandlerType type); ProducerImpl(const ProducerImpl&) = delete; ProducerImpl& operator=(const ProducerImpl&) = delete; @@ -62,6 +63,7 @@ class ProducerImpl : public Producer { GenericRequestHeader GenerateNextSendRequest(const EventHeader& event_header, std::string substream, uint64_t ingest_mode); std::string source_cred_string_; + uint64_t timeout_sec_; }; } diff --git a/producer/api/cpp/src/producer_request.cpp b/producer/api/cpp/src/producer_request.cpp index 53f589e0f0187faf6fb0d662aad0578de4d73826..afe6d271e56f834eaeb4a45b6216d7ff084ef733 100644 --- a/producer/api/cpp/src/producer_request.cpp +++ b/producer/api/cpp/src/producer_request.cpp @@ -16,7 +16,8 @@ ProducerRequest::ProducerRequest(std::string source_credentials, std::string metadata, std::string original_filepath, RequestCallback callback, - bool manage_data_memory) : GenericRequest(std::move(h)), + bool manage_data_memory, + uint64_t timeout_ms) : GenericRequest(std::move(h), timeout_ms), source_credentials{std::move(source_credentials)}, metadata{std::move(metadata)}, data{std::move(data)}, diff --git a/producer/api/cpp/src/producer_request.h b/producer/api/cpp/src/producer_request.h index 4dbf6e8d0da81873c60df92f5ed8313908d7cf08..1dd9a53f81d3b7113b790a5c762d52665b78ece1 100644 --- a/producer/api/cpp/src/producer_request.h +++ b/producer/api/cpp/src/producer_request.h @@ -16,7 +16,8 @@ class ProducerRequest : public GenericRequest { std::string metadata, std::string original_filepath, RequestCallback callback, - bool manage_data_memory); + bool manage_data_memory, + uint64_t timeout_ms); std::string source_credentials; std::string metadata; FileData data; diff --git a/producer/api/cpp/src/request_handler_filesystem.cpp b/producer/api/cpp/src/request_handler_filesystem.cpp index 7c1ff56d4a950cfc8806a69d7520fe302fc48cbf..fd75e003f20a45be41603a7b5d1c1caa1c55a95f 100644 --- a/producer/api/cpp/src/request_handler_filesystem.cpp +++ b/producer/api/cpp/src/request_handler_filesystem.cpp @@ -28,12 +28,16 @@ bool RequestHandlerFilesystem::ProcessRequestUnlocked(GenericRequest* request) { } err = io__->WriteDataToFile(destination_folder_, request->header.message, (uint8_t*)producer_request->data.get(), - (size_t)request->header.data_size, true); + (size_t)request->header.data_size, true, true); if (producer_request->callback) { producer_request->callback(request->header, std::move(err)); } return true; } +void RequestHandlerFilesystem::ProcessRequestTimeout(GenericRequest* request) { + log__->Error("request timeout, id:" + std::to_string(request->header.data_id) + " to " + request->header.substream + + " substream"); +} } diff --git a/producer/api/cpp/src/request_handler_filesystem.h b/producer/api/cpp/src/request_handler_filesystem.h index affdb3fab7ea4fb087b5eab5bc0af6b4cdc4bf72..72fb21061d0d20631aa457f5cf24937c14e0ff1a 100644 --- a/producer/api/cpp/src/request_handler_filesystem.h +++ b/producer/api/cpp/src/request_handler_filesystem.h @@ -23,6 +23,7 @@ class RequestHandlerFilesystem: public RequestHandler { }; void PrepareProcessingRequestLocked() override {}; void TearDownProcessingRequestLocked(bool processing_succeeded) override {}; + void ProcessRequestTimeout(GenericRequest* request) override; virtual ~RequestHandlerFilesystem() = default; std::unique_ptr<IO> io__; diff --git a/producer/api/cpp/src/request_handler_tcp.cpp b/producer/api/cpp/src/request_handler_tcp.cpp index 9aab14b4e513124a33725222d21b7ce997e995ac..a1f25ffb56eef143c1ab0d8003855c827282ea51 100644 --- a/producer/api/cpp/src/request_handler_tcp.cpp +++ b/producer/api/cpp/src/request_handler_tcp.cpp @@ -83,9 +83,6 @@ Error RequestHandlerTcp::ReceiveResponse(const GenericRequestHeader& request_hea return err; } switch (sendDataResponse.error_code) { - case kNetErrorFileIdAlreadyInUse : - return ProducerErrorTemplates::kWrongInput.Generate("file id already in use: " + std::to_string( - request_header.data_id)); case kNetAuthorizationError : { auto res_err = ProducerErrorTemplates::kWrongInput.Generate(); res_err->Append(sendDataResponse.message); @@ -96,6 +93,11 @@ Error RequestHandlerTcp::ReceiveResponse(const GenericRequestHeader& request_hea res_err->Append(sendDataResponse.message); return res_err; } + case kNetErrorWarning: { + auto res_err = ProducerErrorTemplates::kServerWarning.Generate(); + res_err->Append(sendDataResponse.message); + return res_err; + } case kNetErrorNoError : return nullptr; default: @@ -112,14 +114,15 @@ Error RequestHandlerTcp::TrySendToReceiver(const ProducerRequest* request) { } err = ReceiveResponse(request->header); - if (err) { - return err; + if (err == nullptr || err == ProducerErrorTemplates::kServerWarning) { + log__->Debug("successfully sent data, opcode: " + std::to_string(request->header.op_code) + + ", id: " + std::to_string(request->header.data_id) + " to " + connected_receiver_uri_); + if (err == ProducerErrorTemplates::kServerWarning ) { + log__->Warning("warning from server for id " + std::to_string(request->header.data_id) + ": " + err->Explain()); + } } - log__->Debug("successfully sent data, opcode: " + std::to_string(request->header.op_code) + - ", id: " + std::to_string(request->header.data_id) + " to " + connected_receiver_uri_); - - return nullptr; + return err; } @@ -177,10 +180,32 @@ void RequestHandlerTcp::Disconnect() { bool RequestHandlerTcp::ServerError(const Error& err) { return err != nullptr && (err != ProducerErrorTemplates::kWrongInput && - err != ProducerErrorTemplates::kLocalIOError + err != ProducerErrorTemplates::kLocalIOError && + err != ProducerErrorTemplates::kServerWarning ); } +bool RequestHandlerTcp::ProcessErrorFromReceiver(const Error& error, + const ProducerRequest* request, + const std::string& receiver_uri) { + bool is_server_error = ServerError(error); + + if (error && error != ProducerErrorTemplates::kServerWarning) { + Disconnect(); + std::string log_str = "cannot send data, opcode: " + std::to_string(request->header.op_code) + + ", id: " + std::to_string(request->header.data_id) + " to " + receiver_uri + ": " + + error->Explain(); + if (is_server_error) { + log__->Warning(log_str + ", will try again"); + } else { + log__->Error(log_str + ", request removed from queue"); + } + } + + return is_server_error; +} + + bool RequestHandlerTcp::SendDataToOneOfTheReceivers(ProducerRequest* request) { for (auto receiver_uri : receivers_list_) { if (Disconnected()) { @@ -189,13 +214,8 @@ bool RequestHandlerTcp::SendDataToOneOfTheReceivers(ProducerRequest* request) { } auto err = TrySendToReceiver(request); - if (err) { - Disconnect(); - log__->Warning("cannot send data, opcode: " + std::to_string(request->header.op_code) + - ", id: " + std::to_string(request->header.data_id) + " to " + receiver_uri + ": " + - err->Explain()); - } - if (ServerError(err)) { + auto retry = ProcessErrorFromReceiver(err, request, receiver_uri); + if (retry) { continue; } @@ -250,4 +270,17 @@ void RequestHandlerTcp::TearDownProcessingRequestLocked(bool processing_succeede } } +void RequestHandlerTcp::ProcessRequestTimeout(GenericRequest* request) { + auto producer_request = static_cast<ProducerRequest*>(request); + + log__->Error("request timeout, id:" + std::to_string(request->header.data_id) + " to " + request->header.substream + + " substream"); + + auto err = ProducerErrorTemplates::kTimeout.Generate(); + if (producer_request->callback) { + producer_request->callback(request->header, std::move(err)); + } + +} + } diff --git a/producer/api/cpp/src/request_handler_tcp.h b/producer/api/cpp/src/request_handler_tcp.h index 311579b7946cb276f3c1293de87bda889f4223a0..0cba33e5dd747a3564a4bf29c453155ac5e1efaa 100644 --- a/producer/api/cpp/src/request_handler_tcp.h +++ b/producer/api/cpp/src/request_handler_tcp.h @@ -23,6 +23,7 @@ class RequestHandlerTcp: public RequestHandler { bool ReadyProcessRequest() override; void PrepareProcessingRequestLocked() override; void TearDownProcessingRequestLocked(bool processing_succeeded) override; + void ProcessRequestTimeout(GenericRequest* request) override; virtual ~RequestHandlerTcp() = default; std::unique_ptr<IO> io__; @@ -44,10 +45,12 @@ class RequestHandlerTcp: public RequestHandler { bool Disconnected(); void Disconnect(); bool ServerError(const Error& err); - ReceiversList receivers_list_; - system_clock::time_point last_receivers_uri_update_; bool Connected(); bool CanCreateNewConnections(); + bool ProcessErrorFromReceiver(const Error& error, const ProducerRequest* request, const std::string& receiver_uri); + ReceiversList receivers_list_; + system_clock::time_point last_receivers_uri_update_; + uint64_t thread_id_; uint64_t* ncurrent_connections_; std::string connected_receiver_uri_; diff --git a/producer/api/cpp/unittests/test_producer.cpp b/producer/api/cpp/unittests/test_producer.cpp index 4788882c0031a7548f1e140fa8159bf76cf7b01c..807bf22bed02f7b6db2a0ad11d60d83fb264e09a 100644 --- a/producer/api/cpp/unittests/test_producer.cpp +++ b/producer/api/cpp/unittests/test_producer.cpp @@ -15,7 +15,7 @@ namespace { TEST(CreateProducer, TcpProducer) { asapo::Error err; std::unique_ptr<asapo::Producer> producer = asapo::Producer::Create("endpoint", 4, asapo::RequestHandlerType::kTcp, - SourceCredentials{"bt", "", ""}, &err); + SourceCredentials{"bt", "", ""}, 3600, &err); ASSERT_THAT(dynamic_cast<asapo::ProducerImpl*>(producer.get()), Ne(nullptr)); ASSERT_THAT(err, Eq(nullptr)); } @@ -24,7 +24,7 @@ TEST(CreateProducer, ErrorBeamtime) { asapo::Error err; std::string expected_beamtimeid(asapo::kMaxMessageSize * 10, 'a'); std::unique_ptr<asapo::Producer> producer = asapo::Producer::Create("endpoint", 4, asapo::RequestHandlerType::kTcp, - SourceCredentials{expected_beamtimeid, "", ""}, &err); + SourceCredentials{expected_beamtimeid, "", ""}, 3600, &err); ASSERT_THAT(producer, Eq(nullptr)); ASSERT_THAT(err, Eq(asapo::ProducerErrorTemplates::kWrongInput)); } @@ -44,7 +44,7 @@ TEST(CreateProducer, FileSystemProducer) { TEST(CreateProducer, TooManyThreads) { asapo::Error err; std::unique_ptr<asapo::Producer> producer = asapo::Producer::Create("", asapo::kMaxProcessingThreads + 1, - asapo::RequestHandlerType::kTcp, SourceCredentials{"bt", "", ""}, &err); + asapo::RequestHandlerType::kTcp, SourceCredentials{"bt", "", ""}, 3600, &err); ASSERT_THAT(producer, Eq(nullptr)); ASSERT_THAT(err, Eq(asapo::ProducerErrorTemplates::kWrongInput)); } @@ -53,7 +53,7 @@ TEST(CreateProducer, TooManyThreads) { TEST(CreateProducer, ZeroThreads) { asapo::Error err; std::unique_ptr<asapo::Producer> producer = asapo::Producer::Create("", 0, - asapo::RequestHandlerType::kTcp, SourceCredentials{"bt", "", ""}, &err); + asapo::RequestHandlerType::kTcp, SourceCredentials{"bt", "", ""}, 3600, &err); ASSERT_THAT(producer, Eq(nullptr)); ASSERT_THAT(err, Eq(asapo::ProducerErrorTemplates::kWrongInput)); } @@ -62,7 +62,7 @@ TEST(CreateProducer, ZeroThreads) { TEST(Producer, SimpleWorkflowWihoutConnection) { asapo::Error err; std::unique_ptr<asapo::Producer> producer = asapo::Producer::Create("hello", 5, asapo::RequestHandlerType::kTcp, - SourceCredentials{"bt", "", ""}, + SourceCredentials{"bt", "", ""}, 3600, &err); asapo::EventHeader event_header{1, 1, "test"}; diff --git a/producer/api/cpp/unittests/test_producer_impl.cpp b/producer/api/cpp/unittests/test_producer_impl.cpp index 16caa956b7bc2ac27faf33ba608d286e671e1e81..aadeeca48cb08f1c1ad7f7f09cbd30caf7eb8393 100644 --- a/producer/api/cpp/unittests/test_producer_impl.cpp +++ b/producer/api/cpp/unittests/test_producer_impl.cpp @@ -50,7 +50,7 @@ MATCHER_P10(M_CheckSendDataRequest, op_code, source_credentials, metadata, file_ } TEST(ProducerImpl, Constructor) { - asapo::ProducerImpl producer{"", 4, asapo::RequestHandlerType::kTcp}; + asapo::ProducerImpl producer{"", 4, 3600, asapo::RequestHandlerType::kTcp}; ASSERT_THAT(dynamic_cast<asapo::AbstractLogger*>(producer.log__), Ne(nullptr)); ASSERT_THAT(dynamic_cast<asapo::RequestPool*>(producer.request_pool__.get()), Ne(nullptr)); } @@ -61,7 +61,7 @@ class ProducerImplTests : public testing::Test { asapo::ProducerRequestHandlerFactory factory{&service}; testing::NiceMock<asapo::MockLogger> mock_logger; testing::NiceMock<MockRequestPull> mock_pull{&factory, &mock_logger}; - asapo::ProducerImpl producer{"", 1, asapo::RequestHandlerType::kTcp}; + asapo::ProducerImpl producer{"", 1, 3600, asapo::RequestHandlerType::kTcp}; uint64_t expected_size = 100; uint64_t expected_id = 10; uint64_t expected_subset_id = 100; diff --git a/producer/api/cpp/unittests/test_producer_request.cpp b/producer/api/cpp/unittests/test_producer_request.cpp index 2c93230024ebe3f90698fc0beac507760dc4d528..eaf1de84506f54f9a88428552565d17f71e41f45 100644 --- a/producer/api/cpp/unittests/test_producer_request.cpp +++ b/producer/api/cpp/unittests/test_producer_request.cpp @@ -44,7 +44,7 @@ TEST(ProducerRequest, Constructor) { asapo::GenericRequestHeader header{expected_op_code, expected_file_id, expected_file_size, expected_meta_size, expected_file_name}; - asapo::ProducerRequest request{expected_source_credentials, std::move(header), nullptr, expected_meta, "", nullptr, true}; + asapo::ProducerRequest request{expected_source_credentials, std::move(header), nullptr, expected_meta, "", nullptr, true, 0}; ASSERT_THAT(request.source_credentials, Eq(expected_source_credentials)); ASSERT_THAT(request.metadata, Eq(expected_meta)); @@ -62,7 +62,7 @@ TEST(ProducerRequest, Destructor) { char data_[100]; asapo::FileData data{(uint8_t*)data_}; asapo::GenericRequestHeader header{asapo::kOpcodeTransferData, 1, 1, 1, ""}; - asapo::ProducerRequest* request = new asapo::ProducerRequest{"", std::move(header), std::move(data), "", "", nullptr, false}; + asapo::ProducerRequest* request = new asapo::ProducerRequest{"", std::move(header), std::move(data), "", "", nullptr, false, 0}; delete request; diff --git a/producer/api/cpp/unittests/test_request_handler_filesystem.cpp b/producer/api/cpp/unittests/test_request_handler_filesystem.cpp index e36fbbc8eeec7b853a559e0f1d1959283f3e2007..4e443b971714031c162298d4e418cfc6f59682af 100644 --- a/producer/api/cpp/unittests/test_request_handler_filesystem.cpp +++ b/producer/api/cpp/unittests/test_request_handler_filesystem.cpp @@ -61,10 +61,10 @@ class RequestHandlerFilesystemTests : public testing::Test { called = true; callback_err = std::move(err); callback_header = header; - }, true}; + }, true, 0}; - asapo::ProducerRequest request_nocallback{"", header, nullptr, "", "", nullptr, true}; - asapo::ProducerRequest request_filesend{"", header, nullptr, "", expected_origin_fullpath, nullptr, true}; + asapo::ProducerRequest request_nocallback{"", header, nullptr, "", "", nullptr, true, 0}; + asapo::ProducerRequest request_filesend{"", header, nullptr, "", expected_origin_fullpath, nullptr, true, 0}; testing::NiceMock<asapo::MockLogger> mock_logger; @@ -97,7 +97,7 @@ MATCHER_P2(M_CheckSendDataRequest, file_id, file_size, TEST_F(RequestHandlerFilesystemTests, CallBackErrorIfCannotSaveFile) { EXPECT_CALL(mock_io, WriteDataToFile_t(expected_destination, expected_file_name, nullptr, (size_t)expected_file_size, - true)) + true, true)) .WillOnce( Return( asapo::IOErrorTemplates::kUnknownIOError.Generate().release()) @@ -113,7 +113,7 @@ TEST_F(RequestHandlerFilesystemTests, CallBackErrorIfCannotSaveFile) { TEST_F(RequestHandlerFilesystemTests, WorksWithemptyCallback) { EXPECT_CALL(mock_io, WriteDataToFile_t(expected_destination, expected_file_name, nullptr, (size_t) expected_file_size, - true)) + true, true)) .WillOnce( Return(nullptr) ); @@ -149,7 +149,7 @@ TEST_F(RequestHandlerFilesystemTests, FileRequestOK) { )); EXPECT_CALL(mock_io, WriteDataToFile_t(expected_destination, expected_file_name, nullptr, (size_t)expected_file_size, - true)) + true, true)) .WillOnce( Return(nullptr) ); @@ -162,7 +162,7 @@ TEST_F(RequestHandlerFilesystemTests, FileRequestOK) { TEST_F(RequestHandlerFilesystemTests, TransferOK) { EXPECT_CALL(mock_io, WriteDataToFile_t(expected_destination, expected_file_name, nullptr, (size_t) expected_file_size, - true)) + true, true)) .WillOnce( Return( nullptr) diff --git a/producer/api/cpp/unittests/test_request_handler_tcp.cpp b/producer/api/cpp/unittests/test_request_handler_tcp.cpp index d086a932fcdcbe1bbd97878306810c6035561b0c..ab5cebbc6ddb46e94d67ad476d7596fdff2bb6c1 100644 --- a/producer/api/cpp/unittests/test_request_handler_tcp.cpp +++ b/producer/api/cpp/unittests/test_request_handler_tcp.cpp @@ -56,7 +56,7 @@ class RequestHandlerTcpTests : public testing::Test { uint64_t expected_file_size = 1337; uint64_t expected_meta_size = 4; std::string expected_metadata = "meta"; - + std::string expected_warning = "warning"; char expected_file_name[asapo::kMaxMessageSize] = "test_name"; char expected_beamtime_id[asapo::kMaxMessageSize] = "test_beamtime_id"; char expected_substream[asapo::kMaxMessageSize] = "test_substream"; @@ -76,7 +76,7 @@ class RequestHandlerTcpTests : public testing::Test { callback_called = true; callback_err = std::move(err); callback_header = header; - }, true}; + }, true, 0}; std::string expected_origin_fullpath = std::string("origin/") + expected_file_name; asapo::ProducerRequest request_filesend{expected_beamtime_id, header_fromfile, nullptr, expected_metadata, @@ -84,10 +84,10 @@ class RequestHandlerTcpTests : public testing::Test { callback_called = true; callback_err = std::move(err); callback_header = header; - }, true}; + }, true, 0}; - asapo::ProducerRequest request_nocallback{expected_beamtime_id, header, nullptr, expected_metadata, "", nullptr, true}; + asapo::ProducerRequest request_nocallback{expected_beamtime_id, header, nullptr, expected_metadata, "", nullptr, true, 0}; testing::NiceMock<asapo::MockLogger> mock_logger; uint64_t n_connections{0}; asapo::RequestHandlerTcp request_handler{&mock_discovery_service, expected_thread_id, &n_connections}; @@ -116,10 +116,11 @@ class RequestHandlerTcpTests : public testing::Test { void ExpectGetFileSize(bool ok); void ExpectOKSendData(bool only_once = false); void ExpectOKSendFile(bool only_once = false); - void ExpectFailSendFile(const asapo::ProducerErrorTemplate& err_template, bool only_once = false); + void ExpectFailSendFile(const asapo::ProducerErrorTemplate& err_template, bool client_error = false); void ExpectOKSendMetaData(bool only_once = false); void ExpectFailReceive(bool only_once = false); - void ExpectOKReceive(bool only_once = true); + void ExpectOKReceive(bool only_once = true, asapo::NetworkErrorCode code = asapo::kNetErrorNoError, + std::string message = ""); void DoSingleSend(bool connect = true, bool success = true); void AssertImmediatelyCallBack(asapo::NetworkErrorCode error_code, const asapo::ProducerErrorTemplate& err_template); void SetUp() override { @@ -137,10 +138,10 @@ class RequestHandlerTcpTests : public testing::Test { } }; -ACTION_P(A_WriteSendDataResponse, error_code) { +ACTION_P2(A_WriteSendDataResponse, error_code, message) { ((asapo::SendDataResponse*)arg1)->op_code = asapo::kOpcodeTransferData; ((asapo::SendDataResponse*)arg1)->error_code = error_code; - strcpy(((asapo::SendDataResponse*)arg1)->message, expected_auth_message.c_str()); + strcpy(((asapo::SendDataResponse*)arg1)->message, message.c_str()); } MATCHER_P5(M_CheckSendDataRequest, op_code, file_id, file_size, message, substream, @@ -191,7 +192,7 @@ void RequestHandlerTcpTests::ExpectFailAuthorize(bool only_once) { .WillOnce( DoAll( testing::SetArgPointee<3>(nullptr), - A_WriteSendDataResponse(asapo::kNetAuthorizationError), + A_WriteSendDataResponse(asapo::kNetAuthorizationError, expected_auth_message), testing::ReturnArg<2>() )); EXPECT_CALL(mock_io, CloseSocket_t(expected_sd, _)); @@ -230,7 +231,7 @@ void RequestHandlerTcpTests::ExpectOKAuthorize(bool only_once) { .WillOnce( DoAll( testing::SetArgPointee<3>(nullptr), - A_WriteSendDataResponse(asapo::kNetErrorNoError), + A_WriteSendDataResponse(asapo::kNetErrorNoError, expected_auth_message), testing::ReturnArg<2>() )); EXPECT_CALL(mock_logger, Info(AllOf( @@ -275,7 +276,7 @@ void RequestHandlerTcpTests::ExpectFailSendHeader(bool only_once) { EXPECT_CALL(mock_logger, Warning(HasSubstr("put back"))); } -void RequestHandlerTcpTests::ExpectFailSendFile(const asapo::ProducerErrorTemplate& err_template, bool only_once) { +void RequestHandlerTcpTests::ExpectFailSendFile(const asapo::ProducerErrorTemplate& err_template, bool client_error) { int i = 0; for (auto expected_sd : expected_sds) { EXPECT_CALL(mock_io, SendFile_t(expected_sd, expected_origin_fullpath, (size_t) expected_file_size)) @@ -288,13 +289,19 @@ void RequestHandlerTcpTests::ExpectFailSendFile(const asapo::ProducerErrorTempla HasSubstr(receivers_list[i]) ) )); - - EXPECT_CALL(mock_logger, Warning(AllOf( - HasSubstr("cannot send"), - HasSubstr(receivers_list[i]) ) - )); + if (client_error) { + EXPECT_CALL(mock_logger, Error(AllOf( + HasSubstr("cannot send"), + HasSubstr(receivers_list[i]) ) + )); + } else { + EXPECT_CALL(mock_logger, Warning(AllOf( + HasSubstr("cannot send"), + HasSubstr(receivers_list[i]) ) + )); + } EXPECT_CALL(mock_io, CloseSocket_t(expected_sd, _)); - if (only_once) break; + if (client_error) break; i++; } if (err_template != asapo::ProducerErrorTemplates::kLocalIOError.Generate()) { @@ -448,7 +455,7 @@ void RequestHandlerTcpTests::ExpectOKConnect(bool only_once) { } -void RequestHandlerTcpTests::ExpectOKReceive(bool only_once) { +void RequestHandlerTcpTests::ExpectOKReceive(bool only_once, asapo::NetworkErrorCode code, std::string message) { int i = 0; for (auto expected_sd : expected_sds) { EXPECT_CALL(mock_io, Receive_t(expected_sd, _, sizeof(asapo::SendDataResponse), _)) @@ -456,7 +463,7 @@ void RequestHandlerTcpTests::ExpectOKReceive(bool only_once) { .WillOnce( DoAll( testing::SetArgPointee<3>(nullptr), - A_WriteSendDataResponse(asapo::kNetErrorNoError), + A_WriteSendDataResponse(code, message), testing::ReturnArg<2>() )); EXPECT_CALL(mock_logger, Debug(AllOf( @@ -673,7 +680,7 @@ void RequestHandlerTcpTests::AssertImmediatelyCallBack(asapo::NetworkErrorCode e .WillOnce( DoAll( testing::SetArgPointee<3>(nullptr), - A_WriteSendDataResponse(error_code), + A_WriteSendDataResponse(error_code, expected_auth_message), testing::ReturnArg<2>() )); EXPECT_CALL(mock_logger, Debug(AllOf( @@ -682,11 +689,11 @@ void RequestHandlerTcpTests::AssertImmediatelyCallBack(asapo::NetworkErrorCode e ) )); - EXPECT_CALL(mock_logger, Warning(AllOf( - HasSubstr("cannot send"), - HasSubstr(receivers_list[0]) - ) - )); + EXPECT_CALL(mock_logger, Error(AllOf( + HasSubstr("cannot send"), + HasSubstr(receivers_list[0]) + ) + )); request_handler.PrepareProcessingRequestLocked(); auto success = request_handler.ProcessRequestUnlocked(&request); @@ -712,14 +719,12 @@ TEST_F(RequestHandlerTcpTests, ImmediatelyCallBackErrorIfAuthorizationFailure) { AssertImmediatelyCallBack(asapo::kNetAuthorizationError, asapo::ProducerErrorTemplates::kWrongInput); } -TEST_F(RequestHandlerTcpTests, ImmediatelyCallBackErrorIfFileAlreadyInUse) { - AssertImmediatelyCallBack(asapo::kNetErrorFileIdAlreadyInUse, asapo::ProducerErrorTemplates::kWrongInput); -} TEST_F(RequestHandlerTcpTests, ImmediatelyCallBackErrorIfWrongMetadata) { AssertImmediatelyCallBack(asapo::kNetErrorWrongRequest, asapo::ProducerErrorTemplates::kWrongInput); } + TEST_F(RequestHandlerTcpTests, SendEmptyCallBack) { ExpectOKConnect(true); ExpectOKAuthorize(true); @@ -870,5 +875,45 @@ TEST_F(RequestHandlerTcpTests, SendMetaOnlyForFileReadOK) { } +TEST_F(RequestHandlerTcpTests, TimeoutCallsCallback) { + EXPECT_CALL(mock_logger, Error(AllOf( + HasSubstr("timeout"), + HasSubstr("substream")) + )); + + request_handler.ProcessRequestTimeout(&request); + + ASSERT_THAT(callback_err, Eq(asapo::ProducerErrorTemplates::kTimeout)); + ASSERT_THAT(callback_called, Eq(true)); +} + + + +TEST_F(RequestHandlerTcpTests, SendWithWarning) { + ExpectOKConnect(true); + ExpectOKAuthorize(true); + ExpectOKSendAll(true); + ExpectOKReceive(true, asapo::kNetErrorWarning, expected_warning); + + EXPECT_CALL(mock_logger, Warning(AllOf( + HasSubstr("server"), + HasSubstr(expected_warning)) + )); + + + request_handler.PrepareProcessingRequestLocked(); + auto success = request_handler.ProcessRequestUnlocked(&request); + + ASSERT_THAT(success, Eq(true)); + ASSERT_THAT(callback_err, Eq(asapo::ProducerErrorTemplates::kServerWarning)); + ASSERT_THAT(callback_err->Explain(), HasSubstr(expected_warning)); + ASSERT_THAT(callback_called, Eq(true)); + ASSERT_THAT(callback_header.data_size, Eq(header.data_size)); + ASSERT_THAT(callback_header.op_code, Eq(header.op_code)); + ASSERT_THAT(callback_header.data_id, Eq(header.data_id)); + ASSERT_THAT(std::string{callback_header.message}, Eq(std::string{header.message})); +} + + } diff --git a/producer/api/python/asapo_producer.pxd b/producer/api/python/asapo_producer.pxd index 4ce8fda3ba334b5e41bbfd5796205fb06c01240d..22fdedcba6517b42f39381a17f2c19e557c448cb 100644 --- a/producer/api/python/asapo_producer.pxd +++ b/producer/api/python/asapo_producer.pxd @@ -21,6 +21,7 @@ cdef extern from "asapo_producer.h" namespace "asapo": ErrorTemplateInterface kTimeout "asapo::ProducerErrorTemplates::kTimeout" ErrorTemplateInterface kWrongInput "asapo::ProducerErrorTemplates::kWrongInput" ErrorTemplateInterface kLocalIOError "asapo::ProducerErrorTemplates::kLocalIOError" + ErrorTemplateInterface kServerWarning "asapo::ProducerErrorTemplates::kServerWarning" cdef extern from "asapo_producer.h" namespace "asapo": cppclass FileData: @@ -88,7 +89,7 @@ cdef extern from "asapo_wrappers.h" namespace "asapo": cdef extern from "asapo_producer.h" namespace "asapo" nogil: cppclass Producer: @staticmethod - unique_ptr[Producer] Create(string endpoint,uint8_t nthreads,RequestHandlerType type, SourceCredentials source,Error* error) + unique_ptr[Producer] Create(string endpoint,uint8_t nthreads,RequestHandlerType type, SourceCredentials source,uint64_t timeout_sec, Error* error) Error SendFile(const EventHeader& event_header, string substream, string full_path, uint64_t ingest_mode,RequestCallback callback) Error SendData__(const EventHeader& event_header, string substream, void* data, uint64_t ingest_mode,RequestCallback callback) void StopThreads__() diff --git a/producer/api/python/asapo_producer.pyx.in b/producer/api/python/asapo_producer.pyx.in index 48c5fa77afb6ed2747a44c3ad30167da64ff8f50..e606d7d892f1e2b0de8ab5c7858aa29359637f09 100644 --- a/producer/api/python/asapo_producer.pyx.in +++ b/producer/api/python/asapo_producer.pyx.in @@ -47,6 +47,10 @@ class AsapoLocalIOError(AsapoProducerError): class AsapoTimeOutError(AsapoProducerError): pass +class AsapoServerWarning(AsapoProducerError): + pass + + cdef python_exception_from_error(Error& err): error_string = _str(err.get().Explain()) if err == kTimeout: @@ -55,6 +59,8 @@ cdef python_exception_from_error(Error& err): return AsapoWrongInputError(error_string) elif err == kLocalIOError: return AsapoLocalIOError(error_string) + elif err == kServerWarning: + return AsapoServerWarning(error_string) else: return AsapoProducerError(error_string) @@ -272,19 +278,19 @@ cdef class PyProducer: if self.c_producer.get() is not NULL: self.c_producer.get().StopThreads__() @staticmethod - def __create_producer(endpoint,beamtime_id,stream,token,nthreads): + def __create_producer(endpoint,beamtime_id,stream,token,nthreads,timeout_sec): pyProd = PyProducer() cdef Error err cdef SourceCredentials source source.beamtime_id = beamtime_id source.user_token = token source.stream = stream - pyProd.c_producer = Producer.Create(endpoint,nthreads,RequestHandlerType_Tcp,source,&err) + pyProd.c_producer = Producer.Create(endpoint,nthreads,RequestHandlerType_Tcp,source,timeout_sec,&err) if err: throw_exception(err) return pyProd -def create_producer(endpoint,beamtime_id,stream,token,nthreads): +def create_producer(endpoint,beamtime_id,stream,token,nthreads,timeout_sec): """ :param endpoint: server endpoint (url:port) :type endpoint: string @@ -296,11 +302,13 @@ def create_producer(endpoint,beamtime_id,stream,token,nthreads): :type token: string :param nthreads: ingest mode flag :type nthreads: int + :param timeout_sec: send requests timeout + :type timeout_sec: int :raises: AsapoWrongInputError: wrong input (number of threads, ,,,) AsapoProducerError: actually should not happen """ - return PyProducer.__create_producer(_bytes(endpoint),_bytes(beamtime_id),_bytes(stream),_bytes(token),nthreads) + return PyProducer.__create_producer(_bytes(endpoint),_bytes(beamtime_id),_bytes(stream),_bytes(token),nthreads,timeout_sec) __version__ = "@ASAPO_VERSION_PYTHON@" diff --git a/producer/event_monitor_producer/src/main_eventmon.cpp b/producer/event_monitor_producer/src/main_eventmon.cpp index e277e7b111866f18a2fa0c614987bd2b189c577c..cd5899c3c01c0992404c33e733e38c0012e0aadf 100644 --- a/producer/event_monitor_producer/src/main_eventmon.cpp +++ b/producer/event_monitor_producer/src/main_eventmon.cpp @@ -39,7 +39,7 @@ std::unique_ptr<Producer> CreateProducer() { Error err; auto producer = Producer::Create(config->asapo_endpoint, (uint8_t) config->nthreads, - config->mode, asapo::SourceCredentials{config->beamtime_id, config->stream, ""}, &err); + config->mode, asapo::SourceCredentials{config->beamtime_id, config->stream, ""}, 3600, &err); if(err) { std::cerr << "cannot create producer: " << err << std::endl; exit(EXIT_FAILURE); diff --git a/receiver/CMakeLists.txt b/receiver/CMakeLists.txt index c991bcfa85acc096d3ee1fa9548cb77f744c1e4c..182559e7cde925628de70229e91b76e88ca634d6 100644 --- a/receiver/CMakeLists.txt +++ b/receiver/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCE_FILES src/receiver.cpp src/connection.cpp src/request.cpp - src/request_handler_file_write.cpp + src/request_handler_file_process.cpp src/statistics.cpp src/statistics_sender_influx_db.cpp src/receiver_config.cpp @@ -23,9 +23,12 @@ set(SOURCE_FILES src/receiver_statistics.cpp src/request_handler_db_meta_write.cpp src/request_handler_receive_metadata.cpp - src/request_handler_file_receive.cpp + src/request_handler_db_check_request.cpp src/request_factory.cpp - src/request_handler_db.cpp) + src/write_file_processor.cpp + src/request_handler_db.cpp + src/file_processor.cpp + src/receive_file_processor.cpp) ################################ @@ -66,9 +69,9 @@ set(TEST_SOURCE_FILES unittests/test_config.cpp unittests/test_request.cpp unittests/test_request_factory.cpp - unittests/test_request_handler_file_write.cpp - unittests/test_request_handler_file_receive.cpp + unittests/test_request_handler_file_process.cpp unittests/test_request_handler_db_writer.cpp + unittests/test_request_handler_db_check_request.cpp unittests/test_request_handler_db_meta_writer.cpp unittests/test_request_handler_db.cpp unittests/test_request_handler_authorizer.cpp @@ -79,6 +82,8 @@ set(TEST_SOURCE_FILES unittests/mock_receiver_config.cpp unittests/test_requests_dispatcher.cpp unittests/test_datacache.cpp + unittests/test_write_file_processor.cpp + unittests/test_receive_file_processor.cpp ) # set(TEST_LIBRARIES "${TARGET_NAME};system_io") diff --git a/receiver/src/file_processor.cpp b/receiver/src/file_processor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8425048e13565ba03d4d26b095b9b24189fabbe --- /dev/null +++ b/receiver/src/file_processor.cpp @@ -0,0 +1,12 @@ +#include "file_processor.h" + +#include "io/io_factory.h" +#include "receiver_logger.h" + +namespace asapo { + +FileProcessor::FileProcessor(): io__{GenerateDefaultIO()}, log__{GetDefaultReceiverLogger()} { + +} + +} \ No newline at end of file diff --git a/receiver/src/file_processor.h b/receiver/src/file_processor.h new file mode 100644 index 0000000000000000000000000000000000000000..b57ca733c3db3c4d42df5b36312c41702ccc868c --- /dev/null +++ b/receiver/src/file_processor.h @@ -0,0 +1,22 @@ +#ifndef ASAPO_FILE_PROCESSOR_H +#define ASAPO_FILE_PROCESSOR_H + +#include "io/io.h" +#include "logger/logger.h" + +namespace asapo { + +class Request; + +class FileProcessor { + public: + FileProcessor(); + virtual ~FileProcessor() = default; + virtual Error ProcessFile(const Request* request, bool overwrite) const = 0; + std::unique_ptr<IO> io__; + const AbstractLogger* log__; +}; + +} + +#endif //ASAPO_FILE_PROCESSOR_H diff --git a/receiver/src/request_handler_file_receive.cpp b/receiver/src/receive_file_processor.cpp similarity index 58% rename from receiver/src/request_handler_file_receive.cpp rename to receiver/src/receive_file_processor.cpp index 500f6a9cd1a72ffe3c93a17923ad8203e9b7a844..512ca14386a88bf52dda49f6a2980830367cf84a 100644 --- a/receiver/src/request_handler_file_receive.cpp +++ b/receiver/src/receive_file_processor.cpp @@ -1,31 +1,27 @@ -#include "request_handler_file_receive.h" +#include "receive_file_processor.h" + #include "io/io_factory.h" +#include "receiver_error.h" +#include "preprocessor/definitions.h" #include "request.h" -#include "receiver_logger.h" #include "receiver_config.h" -#include "preprocessor/definitions.h" namespace asapo { -Error RequestHandlerFileReceive::ProcessRequest(Request* request) const { +ReceiveFileProcessor::ReceiveFileProcessor() : FileProcessor() { + +} + +Error ReceiveFileProcessor::ProcessFile(const Request* request, bool overwrite) const { auto fsize = request->GetDataSize(); auto socket = request->GetSocket(); auto fname = request->GetFileName(); auto root_folder = request->GetFullPath(GetReceiverConfig()->root_folder); - auto err = io__->ReceiveDataToFile(socket, root_folder, fname, (size_t) fsize, true); + auto err = io__->ReceiveDataToFile(socket, root_folder, fname, (size_t) fsize, true, overwrite); if (!err) { log__->Debug("received file of size " + std::to_string(fsize) + " to " + root_folder + kPathSeparator + fname); } return err; } -RequestHandlerFileReceive::RequestHandlerFileReceive() : io__{GenerateDefaultIO()} , log__{GetDefaultReceiverLogger()} { - -} - -StatisticEntity RequestHandlerFileReceive::GetStatisticEntity() const { - return StatisticEntity::kDisk; -} - - -} +} \ No newline at end of file diff --git a/receiver/src/receive_file_processor.h b/receiver/src/receive_file_processor.h new file mode 100644 index 0000000000000000000000000000000000000000..9941e17957ac39f3370d32757dc6cebef321264a --- /dev/null +++ b/receiver/src/receive_file_processor.h @@ -0,0 +1,17 @@ +#ifndef ASAPO_RECEIVE_FILE_PROCESSOR_H +#define ASAPO_RECEIVE_FILE_PROCESSOR_H + +#include "file_processor.h" + +namespace asapo { + +class ReceiveFileProcessor final : public FileProcessor { + public: + ReceiveFileProcessor(); + Error ProcessFile(const Request* request, bool overwrite) const override; +}; + + +} + +#endif //ASAPO_RECEIVE_FILE_PROCESSOR_H diff --git a/receiver/src/receiver_data_server/receiver_data_server_request.cpp b/receiver/src/receiver_data_server/receiver_data_server_request.cpp index f0e5a5deade1ffb76ecabc493fa7aee3b2e88ae4..6c30cff9e646cc4b18e7d6ed308f1a6c4b913d68 100644 --- a/receiver/src/receiver_data_server/receiver_data_server_request.cpp +++ b/receiver/src/receiver_data_server/receiver_data_server_request.cpp @@ -4,7 +4,7 @@ namespace asapo { ReceiverDataServerRequest::ReceiverDataServerRequest(GenericRequestHeader header, uint64_t source_id) : - GenericRequest(std::move(header)), + GenericRequest(std::move(header), 0), source_id{source_id} { } diff --git a/receiver/src/receiver_data_server/receiver_data_server_request_handler.cpp b/receiver/src/receiver_data_server/receiver_data_server_request_handler.cpp index 1254981a8dd63534dca2d3c26c885c0e08f50716..f3d1672a8f05c144be3ac4bdd602bec2387e203b 100644 --- a/receiver/src/receiver_data_server/receiver_data_server_request_handler.cpp +++ b/receiver/src/receiver_data_server/receiver_data_server_request_handler.cpp @@ -86,4 +86,8 @@ Error ReceiverDataServerRequestHandler::SendResponce(const ReceiverDataServerReq return server_->SendData(request->source_id, &responce, sizeof(GenericNetworkResponse)); } +void ReceiverDataServerRequestHandler::ProcessRequestTimeout(GenericRequest* request) { +// do nothing +} + } \ No newline at end of file diff --git a/receiver/src/receiver_data_server/receiver_data_server_request_handler.h b/receiver/src/receiver_data_server/receiver_data_server_request_handler.h index e5aabd418fa606d3f2e3e74787b1f15a776af2d0..0ca1a46b5fb0c8d789a389e54eddc092e652f057 100644 --- a/receiver/src/receiver_data_server/receiver_data_server_request_handler.h +++ b/receiver/src/receiver_data_server/receiver_data_server_request_handler.h @@ -17,6 +17,8 @@ class ReceiverDataServerRequestHandler: public RequestHandler { bool ReadyProcessRequest() override; void PrepareProcessingRequestLocked() override; void TearDownProcessingRequestLocked(bool processing_succeeded) override; + void ProcessRequestTimeout(GenericRequest* request) override; + const AbstractLogger* log__; Statistics* statistics__; private: diff --git a/receiver/src/receiver_error.h b/receiver/src/receiver_error.h index 0704a69bc0335145f62dba3b7af0f43c791af6e0..a25c9e78f79e4760e8d22e6bf81e33f16799265b 100644 --- a/receiver/src/receiver_error.h +++ b/receiver/src/receiver_error.h @@ -8,24 +8,25 @@ namespace asapo { enum class ReceiverErrorType { kInvalidOpCode, kBadRequest, - kReject, kAuthorizationFailure, kInternalServerError, + kWarningDuplicatedRequest }; using ReceiverErrorTemplate = ServiceErrorTemplate<ReceiverErrorType, ErrorType::kReceiverError>; namespace ReceiverErrorTemplates { -auto const kInvalidOpCode = ReceiverErrorTemplate{ - "Invalid Opcode", ReceiverErrorType::kInvalidOpCode -}; -auto const kReject = ReceiverErrorTemplate{ - "request rejected", ReceiverErrorType::kReject +auto const kWarningDuplicatedRequest = ReceiverErrorTemplate{ + "Duplicated request, possible due to retry", ReceiverErrorType::kWarningDuplicatedRequest }; +auto const kInvalidOpCode = ReceiverErrorTemplate{ + "Invalid Opcode", ReceiverErrorType::kInvalidOpCode +}; + auto const kInternalServerError = ReceiverErrorTemplate{ "server error", ReceiverErrorType::kInternalServerError }; diff --git a/receiver/src/request.cpp b/receiver/src/request.cpp index 2a5cec65f56ef216019c12659a2b44c88d882f60..c34a9f421e200f0638c438f63f9a9cdcf5070c83 100644 --- a/receiver/src/request.cpp +++ b/receiver/src/request.cpp @@ -5,9 +5,11 @@ namespace asapo { Request::Request(const GenericRequestHeader& header, - SocketDescriptor socket_fd, std::string origin_uri, DataCache* cache) : io__{GenerateDefaultIO()}, + SocketDescriptor socket_fd, std::string origin_uri, DataCache* cache, + const RequestHandlerDbCheckRequest* db_check_handler) : io__{GenerateDefaultIO()}, cache__{cache}, request_header_(header), - socket_fd_{socket_fd}, origin_uri_{std::move(origin_uri)} { + socket_fd_{socket_fd}, origin_uri_{std::move(origin_uri)}, + check_duplicate_request_handler_{db_check_handler} { } Error Request::PrepareDataBufferAndLockIfNeeded() { @@ -177,4 +179,24 @@ std::string Request::GetFullPath(std::string root_folder) const { + GetBeamtimeId(); } +bool Request::WasAlreadyProcessed() const { + return already_processed_; +} + +void Request::SetAlreadyProcessedFlag() { + already_processed_ = true; +} + +void Request::SetWarningMessage(std::string message) { + warning_message_ = std::move(message); +} + +const std::string& Request::GetWarningMessage() const { + return warning_message_; +} + +Error Request::CheckForDuplicates() { + return check_duplicate_request_handler_->ProcessRequest(this); +} + } \ No newline at end of file diff --git a/receiver/src/request.h b/receiver/src/request.h index 7af37f61b48b548d8da0dd83d5389725836e5e01..11eb6ca2a60ba7e7b3e27be5aa816fa77b7c86d5 100644 --- a/receiver/src/request.h +++ b/receiver/src/request.h @@ -7,13 +7,13 @@ #include "common/networking.h" #include "io/io.h" #include "request_handler.h" -#include "request_handler_file_write.h" +#include "request_handler_file_process.h" #include "request_handler_db_write.h" #include "request_handler_authorize.h" #include "request_handler_db_meta_write.h" #include "request_handler_receive_data.h" #include "request_handler_receive_metadata.h" -#include "request_handler_file_receive.h" +#include "request_handler_db_check_request.h" #include "receiver_statistics.h" #include "data_cache.h" @@ -27,8 +27,9 @@ class Request { public: VIRTUAL Error Handle(ReceiverStatistics*); ~Request() = default; + Request() = delete; Request(const GenericRequestHeader& request_header, SocketDescriptor socket_fd, std::string origin_uri, - DataCache* cache); + DataCache* cache, const RequestHandlerDbCheckRequest* db_check_handler); VIRTUAL void AddHandler(const ReceiverRequestHandler*); VIRTUAL const RequestHandlerList& GetListHandlers() const; VIRTUAL uint64_t GetDataSize() const; @@ -66,6 +67,11 @@ class Request { std::unique_ptr<IO> io__; DataCache* cache__ = nullptr; VIRTUAL uint64_t GetSlotId() const; + VIRTUAL bool WasAlreadyProcessed() const; + VIRTUAL void SetAlreadyProcessedFlag(); + VIRTUAL void SetWarningMessage(std::string message); + VIRTUAL const std::string& GetWarningMessage() const; + VIRTUAL Error CheckForDuplicates(); private: const GenericRequestHeader request_header_; const SocketDescriptor socket_fd_; @@ -80,6 +86,9 @@ class Request { std::string beamtime_year_; std::string metadata_; CacheMeta* slot_meta_ = nullptr; + bool already_processed_ = false; + std::string warning_message_; + const RequestHandlerDbCheckRequest* check_duplicate_request_handler_; }; diff --git a/receiver/src/request_factory.cpp b/receiver/src/request_factory.cpp index 27f31cb644d6d4bcc9e80504012be984b9ae83ed..a01bc4803156d4d55d9725fad9bc2e1e5e04d104 100644 --- a/receiver/src/request_factory.cpp +++ b/receiver/src/request_factory.cpp @@ -38,7 +38,7 @@ void RequestFactory::AddReceiveViaBufferHandlers(std::unique_ptr<Request>& reque Error RequestFactory::AddReceiveDirectToFileHandler(std::unique_ptr<Request>& request, const GenericRequestHeader& request_header) const { if (!GetReceiverConfig()->write_to_disk) { - return ReceiverErrorTemplates::kReject.Generate("reciever does not support writing to disk"); + return ReceiverErrorTemplates::kInternalServerError.Generate("reciever does not support writing to disk"); } if (! (request_header.custom_data[kPosIngestMode] & kStoreInFilesystem)) { return ReceiverErrorTemplates::kBadRequest.Generate("ingest mode should include kStoreInFilesystem for large files "); @@ -69,7 +69,7 @@ Error RequestFactory::AddHandlersToRequest(std::unique_ptr<Request>& request, request->AddHandler(&request_handler_receivedata_); request->AddHandler(&request_handler_db_meta_write_); } else { - return ReceiverErrorTemplates::kReject.Generate("reciever does not support writing to database"); + return ReceiverErrorTemplates::kInternalServerError.Generate("reciever does not support writing to database"); } break; } @@ -88,7 +88,9 @@ Error RequestFactory::AddHandlersToRequest(std::unique_ptr<Request>& request, std::unique_ptr<Request> RequestFactory::GenerateRequest(const GenericRequestHeader& request_header, SocketDescriptor socket_fd, std::string origin_uri, Error* err) const noexcept { - auto request = std::unique_ptr<Request> {new Request{request_header, socket_fd, std::move(origin_uri), cache_.get()}}; + auto request = std::unique_ptr<Request> {new Request{request_header, socket_fd, std::move(origin_uri), cache_.get(), + &request_handler_db_check_} + }; *err = AddHandlersToRequest(request, request_header); if (*err) { return nullptr; diff --git a/receiver/src/request_factory.h b/receiver/src/request_factory.h index 90ce81c282243906ce30e495a967243e224f6dfb..364225421de4f9c7478ef4189601aba24843fb5b 100644 --- a/receiver/src/request_factory.h +++ b/receiver/src/request_factory.h @@ -2,6 +2,8 @@ #define ASAPO_REQUEST_FACTORY_H #include "request.h" +#include "write_file_processor.h" +#include "receive_file_processor.h" namespace asapo { @@ -13,13 +15,16 @@ class RequestFactory { private: Error AddHandlersToRequest(std::unique_ptr<Request>& request, const GenericRequestHeader& request_header) const; Error AddReceiveWriteHandlers(std::unique_ptr<Request>& request, const GenericRequestHeader& request_header) const; - RequestHandlerFileWrite request_handler_filewrite_; + WriteFileProcessor write_file_processor_; + ReceiveFileProcessor receive_file_processor_; + RequestHandlerFileProcess request_handler_filewrite_{&write_file_processor_}; + RequestHandlerFileProcess request_handler_filereceive_{&receive_file_processor_}; RequestHandlerReceiveData request_handler_receivedata_; RequestHandlerReceiveMetaData request_handler_receive_metadata_; RequestHandlerDbWrite request_handler_dbwrite_{kDBDataCollectionNamePrefix}; RequestHandlerDbMetaWrite request_handler_db_meta_write_{kDBMetaCollectionName}; RequestHandlerAuthorize request_handler_authorize_; - RequestHandlerFileReceive request_handler_filereceive_; + RequestHandlerDbCheckRequest request_handler_db_check_{kDBDataCollectionNamePrefix};; SharedCache cache_; bool ReceiveDirectToFile(const GenericRequestHeader& request_header) const; Error AddReceiveDirectToFileHandler(std::unique_ptr<Request>& request, diff --git a/receiver/src/request_handler_db_check_request.cpp b/receiver/src/request_handler_db_check_request.cpp new file mode 100644 index 0000000000000000000000000000000000000000..03d5393a44b0b4ee14919b18af711f15fa651fd1 --- /dev/null +++ b/receiver/src/request_handler_db_check_request.cpp @@ -0,0 +1,72 @@ +#include "request_handler_db_check_request.h" + +#include "database/database.h" +#include "database/db_error.h" +#include "logger/logger.h" +#include "request_handler_db.h" +#include "receiver_config.h" +#include "io/io.h" +#include "request.h" + +namespace asapo { + +RequestHandlerDbCheckRequest::RequestHandlerDbCheckRequest(std::string collection_name_prefix) : RequestHandlerDb( + std::move( + collection_name_prefix)) { + +} + +Error RequestHandlerDbCheckRequest::GetRecordFromDb(const Request* request, FileInfo* record ) const { + auto op_code = request->GetOpCode(); + auto id = request->GetDataID(); + auto col_name = collection_name_prefix_ + "_" + request->GetSubstream(); + Error err; + if (op_code == Opcode::kOpcodeTransferData) { + err = db_client__->GetById(col_name, id, record); + if (!err) { + log__->Debug(std::string{"get record id "} + std::to_string(id) + " from " + col_name + " in " + + db_name_ + " at " + GetReceiverConfig()->database_uri); + } + return err; + } else { + auto subset_id = request->GetCustomData()[1]; + err = db_client__->GetDataSetById(col_name, subset_id, id, record); + if (!err) { + log__->Debug(std::string{"get subset record id "} + std::to_string(subset_id) + " from " + col_name + " in " + + db_name_ + " at " + GetReceiverConfig()->database_uri); + } + return err; + } +} + + +bool RequestHandlerDbCheckRequest::SameRequestInRecord(const Request* request, const FileInfo& record) const { + std::string meta = request->GetMetaData(); + if (meta.size() == 0) { // so it is stored in database + meta = "{}"; + } + return request->GetDataSize() == record.size + && request->GetFileName() == record.name + && meta == record.metadata; +} + +Error RequestHandlerDbCheckRequest::ProcessRequest(Request* request) const { + if (auto err = RequestHandlerDb::ProcessRequest(request) ) { + return err; + } + + FileInfo record; + auto err = GetRecordFromDb(request, &record); + if (err) { + return err == DBErrorTemplates::kNoRecord ? nullptr : std::move(err); + } + + if (SameRequestInRecord(request, record)) { + return ReceiverErrorTemplates::kWarningDuplicatedRequest.Generate(); + } else { + return ReceiverErrorTemplates::kBadRequest.Generate("already have record with same id"); + } +} + + +} \ No newline at end of file diff --git a/receiver/src/request_handler_db_check_request.h b/receiver/src/request_handler_db_check_request.h new file mode 100644 index 0000000000000000000000000000000000000000..c5efd11009fdf3d7c919ed6b96e49a7fe08c0d86 --- /dev/null +++ b/receiver/src/request_handler_db_check_request.h @@ -0,0 +1,24 @@ +#ifndef ASAPO_REQUEST_HANDLER_DB_CHECK_REQUEST_H +#define ASAPO_REQUEST_HANDLER_DB_CHECK_REQUEST_H + +#include "request_handler.h" +#include "database/database.h" +#include "request_handler_db.h" +#include "io/io.h" +#include "preprocessor/definitions.h" + +namespace asapo { + +class RequestHandlerDbCheckRequest FINAL : public RequestHandlerDb { + public: + RequestHandlerDbCheckRequest(std::string collection_name_prefix); + Error ProcessRequest(Request* request) const override; + private: + Error GetRecordFromDb(const Request* request, FileInfo* record) const; + bool SameRequestInRecord(const Request* request, const FileInfo& record) const; + +}; + +} + +#endif //ASAPO_REQUEST_HANDLER_DB_CHECK_REQUEST_H diff --git a/receiver/src/request_handler_db_write.cpp b/receiver/src/request_handler_db_write.cpp index e2d3c656cf330f839d8f3b21e04829ddd275b4c9..6a0e31bcb708d899b38acb7650b1f763ad0488b8 100644 --- a/receiver/src/request_handler_db_write.cpp +++ b/receiver/src/request_handler_db_write.cpp @@ -3,6 +3,7 @@ #include "receiver_config.h" #include "receiver_logger.h" #include "io/io_factory.h" +#include "database/db_error.h" namespace asapo { @@ -17,13 +18,35 @@ std::string string_format( const std::string& format, Args ... args ) { Error RequestHandlerDbWrite::ProcessRequest(Request* request) const { - if (Error err = RequestHandlerDb::ProcessRequest(request) ) { + if (request->WasAlreadyProcessed()) { + return nullptr; + } + + if (auto err = RequestHandlerDb::ProcessRequest(request) ) { + return err; + } + + auto err = InsertRecordToDb(request); + if (err == DBErrorTemplates::kDuplicateID) { + return ProcessDuplicateRecordSituation(request); + } else { return err; } +} + +Error RequestHandlerDbWrite::ProcessDuplicateRecordSituation(Request* request) const { + auto check_err = request->CheckForDuplicates(); + if (check_err == ReceiverErrorTemplates::kWarningDuplicatedRequest) { + std::string warn_str = "ignoring duplicate record for id " + std::to_string(request->GetDataID()); + request->SetWarningMessage(warn_str); + log__->Warning(warn_str); + return nullptr; + } - return InsertRecordToDb(request); + return check_err; } + Error RequestHandlerDbWrite::InsertRecordToDb(const Request* request) const { auto file_info = PrepareFileInfo(request); @@ -31,7 +54,7 @@ Error RequestHandlerDbWrite::InsertRecordToDb(const Request* request) const { auto col_name = collection_name_prefix_ + "_" + request->GetSubstream(); Error err; if (op_code == Opcode::kOpcodeTransferData) { - err = db_client__->Insert(col_name, file_info, true); + err = db_client__->Insert(col_name, file_info, false); if (!err) { log__->Debug(std::string{"insert record id "} + std::to_string(file_info.id) + " to " + col_name + " in " + db_name_ + @@ -40,7 +63,7 @@ Error RequestHandlerDbWrite::InsertRecordToDb(const Request* request) const { } else { auto subset_id = request->GetCustomData()[1]; auto subset_size = request->GetCustomData()[2]; - err = db_client__->InsertAsSubset(col_name, file_info, subset_id, subset_size, true); + err = db_client__->InsertAsSubset(col_name, file_info, subset_id, subset_size, false); if (!err) { log__->Debug(std::string{"insert record as subset id "} + std::to_string(subset_id) + ", id: " + std::to_string(file_info.id) + " to " + col_name + " in " + @@ -62,9 +85,11 @@ FileInfo RequestHandlerDbWrite::PrepareFileInfo(const Request* request) const { file_info.metadata = request->GetMetaData(); return file_info; } + RequestHandlerDbWrite::RequestHandlerDbWrite(std::string collection_name_prefix) : RequestHandlerDb(std::move( collection_name_prefix)) { } + } diff --git a/receiver/src/request_handler_db_write.h b/receiver/src/request_handler_db_write.h index 56a9c00a8a68fa88e95837ee1a8c46036abaea4b..4603c028f1cfa8ce2064af7d338215da055c4139 100644 --- a/receiver/src/request_handler_db_write.h +++ b/receiver/src/request_handler_db_write.h @@ -16,6 +16,8 @@ class RequestHandlerDbWrite final: public RequestHandlerDb { private: FileInfo PrepareFileInfo(const Request* request) const; Error InsertRecordToDb(const Request* request) const; + Error ProcessDuplicateRecordSituation(Request* request) const; + }; } diff --git a/receiver/src/request_handler_file_process.cpp b/receiver/src/request_handler_file_process.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fde5496a2e7d753b839586cb9167e0765b3ca120 --- /dev/null +++ b/receiver/src/request_handler_file_process.cpp @@ -0,0 +1,48 @@ +#include "request_handler_file_process.h" +#include "io/io_factory.h" +#include "request.h" +#include "receiver_logger.h" +#include "receiver_config.h" +#include "preprocessor/definitions.h" + +namespace asapo { + +Error RequestHandlerFileProcess::ProcessRequest(Request* request) const { + + auto err = file_processor_->ProcessFile(request, false); + if (err == IOErrorTemplates::kFileAlreadyExists) { + return ProcessFileExistSituation(request); + } + + return err; +} + +Error RequestHandlerFileProcess::ProcessFileExistSituation(Request* request) const { + auto err_duplicate = request->CheckForDuplicates(); + if (err_duplicate == nullptr) { + request->SetWarningMessage("file has been overwritten"); + log__->Warning("overwriting file " + request->GetFullPath(GetReceiverConfig()->root_folder)); + return file_processor_->ProcessFile(request, true); + } + + if (err_duplicate == ReceiverErrorTemplates::kWarningDuplicatedRequest) { + request->SetAlreadyProcessedFlag(); + request->SetWarningMessage("duplicated request, ignored"); + log__->Warning("duplicated request, id: " + std::to_string(request->GetDataID())); + return nullptr; + } + + return err_duplicate; +} + + +RequestHandlerFileProcess::RequestHandlerFileProcess(const FileProcessor* file_processor) : io__{GenerateDefaultIO()}, + log__{GetDefaultReceiverLogger()}, file_processor_{file_processor} { + +} + +StatisticEntity RequestHandlerFileProcess::GetStatisticEntity() const { + return StatisticEntity::kDisk; +} + +} diff --git a/receiver/src/request_handler_file_process.h b/receiver/src/request_handler_file_process.h new file mode 100644 index 0000000000000000000000000000000000000000..d4db2b5f5dcd3a1583d1f0cc931a5cad3152e8d1 --- /dev/null +++ b/receiver/src/request_handler_file_process.h @@ -0,0 +1,25 @@ +#ifndef ASAPO_REQUEST_HANDLER_FILE_PROCESS_H +#define ASAPO_REQUEST_HANDLER_FILE_PROCESS_H + +#include "request_handler.h" +#include "logger/logger.h" +#include "file_processor.h" +#include "io/io.h" + +namespace asapo { + +class RequestHandlerFileProcess final : public ReceiverRequestHandler { + public: + RequestHandlerFileProcess() = delete; + RequestHandlerFileProcess(const FileProcessor* file_processor); + StatisticEntity GetStatisticEntity() const override; + Error ProcessRequest(Request* request) const override; + std::unique_ptr<IO> io__; + const AbstractLogger* log__; + private: + Error ProcessFileExistSituation(Request* request) const; + const FileProcessor* file_processor_; + +}; +} +#endif //ASAPO_REQUEST_HANDLER_FILE_PROCESS_H diff --git a/receiver/src/request_handler_file_receive.h b/receiver/src/request_handler_file_receive.h deleted file mode 100644 index 4e9cdb4b5186bcc5379a2ea6e8dfc0c4dbd2e0a5..0000000000000000000000000000000000000000 --- a/receiver/src/request_handler_file_receive.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ASAPO_REQUEST_HANDLER_FILE_RECEIVE_H -#define ASAPO_REQUEST_HANDLER_FILE_RECEIVE_H - -#include "request_handler.h" -#include "logger/logger.h" - -#include "io/io.h" - -namespace asapo { - -class RequestHandlerFileReceive final: public ReceiverRequestHandler { - public: - RequestHandlerFileReceive(); - StatisticEntity GetStatisticEntity() const override; - Error ProcessRequest(Request* request) const override; - std::unique_ptr<IO> io__; - const AbstractLogger* log__; -}; - -} - -#endif //ASAPO_REQUEST_HANDLER_FILE_RECEIVE_H diff --git a/receiver/src/request_handler_file_write.h b/receiver/src/request_handler_file_write.h deleted file mode 100644 index 8e77f808642d6e68a169f58ac4884aead74dc7c5..0000000000000000000000000000000000000000 --- a/receiver/src/request_handler_file_write.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ASAPO_REQUEST_HANDLER_FILE_WRITE_H -#define ASAPO_REQUEST_HANDLER_FILE_WRITE_H - -#include "request_handler.h" -#include "logger/logger.h" - -#include "io/io.h" - -namespace asapo { - -class RequestHandlerFileWrite final: public ReceiverRequestHandler { - public: - RequestHandlerFileWrite(); - StatisticEntity GetStatisticEntity() const override; - Error ProcessRequest(Request* request) const override; - std::unique_ptr<IO> io__; - const AbstractLogger* log__; -}; - -} - -#endif //ASAPO_REQUEST_HANDLER_FILE_WRITE_H diff --git a/receiver/src/requests_dispatcher.cpp b/receiver/src/requests_dispatcher.cpp index 4ac17c30dfec9c13d1afcb72fa14717c7d52c5a7..93c1cade3aaa67958c4f674d41c24b5b27eef0da 100644 --- a/receiver/src/requests_dispatcher.cpp +++ b/receiver/src/requests_dispatcher.cpp @@ -16,9 +16,7 @@ producer_uri_{std::move(address)} { NetworkErrorCode GetNetworkCodeFromError(const Error& err) { if (err) { - if (err == IOErrorTemplates::kFileAlreadyExists) { - return NetworkErrorCode::kNetErrorFileIdAlreadyInUse; - } else if (err == ReceiverErrorTemplates::kAuthorizationFailure) { + if (err == ReceiverErrorTemplates::kAuthorizationFailure) { return NetworkErrorCode::kNetAuthorizationError; } else if (err == DBErrorTemplates::kJsonParseError || err == ReceiverErrorTemplates::kBadRequest) { return NetworkErrorCode::kNetErrorWrongRequest; @@ -29,25 +27,47 @@ NetworkErrorCode GetNetworkCodeFromError(const Error& err) { return NetworkErrorCode::kNetErrorNoError; } -Error RequestsDispatcher::ProcessRequest(const std::unique_ptr<Request>& request) const noexcept { +GenericNetworkResponse RequestsDispatcher::CreateResponseToRequest(const std::unique_ptr<Request>& request, + const Error& handle_error) const { + GenericNetworkResponse generic_response; + generic_response.op_code = request->GetOpCode(); + generic_response.error_code = GetNetworkCodeFromError(handle_error); + strcpy(generic_response.message, ""); + if (handle_error) { + strncpy(generic_response.message, handle_error->Explain().c_str(), kMaxMessageSize); + } + if (request->GetWarningMessage().size() > 0) { + generic_response.error_code = kNetErrorWarning; + strncpy(generic_response.message, request->GetWarningMessage().c_str(), kMaxMessageSize); + } + return generic_response; +} + +Error RequestsDispatcher::HandleRequest(const std::unique_ptr<Request>& request) const { log__->Debug("processing request id " + std::to_string(request->GetDataID()) + ", opcode " + std::to_string(request->GetOpCode()) + " from " + producer_uri_ ); Error handle_err; handle_err = request->Handle(statistics__); - GenericNetworkResponse generic_response; - generic_response.op_code = request->GetOpCode(); - generic_response.error_code = GetNetworkCodeFromError(handle_err); - strcpy(generic_response.message, ""); if (handle_err) { log__->Error("error processing request from " + producer_uri_ + " - " + handle_err->Explain()); - strncpy(generic_response.message, handle_err->Explain().c_str(), kMaxMessageSize); } + return handle_err; +} + +Error RequestsDispatcher::SendResponse(const std::unique_ptr<Request>& request, const Error& handle_error) const { log__->Debug("sending response to " + producer_uri_ ); Error io_err; + GenericNetworkResponse generic_response = CreateResponseToRequest(request, handle_error); io__->Send(socket_fd_, &generic_response, sizeof(GenericNetworkResponse), &io_err); if (io_err) { log__->Error("error sending response to " + producer_uri_ + " - " + io_err->Explain()); } + return io_err; +} + +Error RequestsDispatcher::ProcessRequest(const std::unique_ptr<Request>& request) const noexcept { + auto handle_err = HandleRequest(request); + auto io_err = SendResponse(request, handle_err); return handle_err == nullptr ? std::move(io_err) : std::move(handle_err); } @@ -61,20 +81,15 @@ std::unique_ptr<Request> RequestsDispatcher::GetNextRequest(Error* err) const no if (*err == ErrorTemplates::kEndOfFile) { log__->Debug("error getting next request from " + producer_uri_ + " - " + "peer has performed an orderly shutdown"); } else { - log__->Error("error getting next request from " + producer_uri_ + " - " + (*err)-> - Explain() - ); + log__->Error("error getting next request from " + producer_uri_ + " - " + (*err)->Explain()); } return nullptr; } statistics__-> StopTimer(); auto request = request_factory__->GenerateRequest(generic_request_header, socket_fd_, producer_uri_, err); if (*err) { - log__->Error("error processing request from " + producer_uri_ + " - " + (*err)-> - Explain() - ); + log__->Error("error processing request from " + producer_uri_ + " - " + (*err)->Explain()); } - return request; } diff --git a/receiver/src/requests_dispatcher.h b/receiver/src/requests_dispatcher.h index 9389a3d9752e16f3789b2921c7454b0b74c5cad9..047c1b5efb3bb4a95ca816a216f1d36a2162f83d 100644 --- a/receiver/src/requests_dispatcher.h +++ b/receiver/src/requests_dispatcher.h @@ -24,6 +24,10 @@ class RequestsDispatcher { private: SocketDescriptor socket_fd_; std::string producer_uri_; + GenericNetworkResponse CreateResponseToRequest(const std::unique_ptr<Request>& request, + const Error& handle_error) const; + Error HandleRequest(const std::unique_ptr<Request>& request) const; + Error SendResponse(const std::unique_ptr<Request>& request, const Error& handle_error) const; }; } diff --git a/receiver/src/request_handler_file_write.cpp b/receiver/src/write_file_processor.cpp similarity index 62% rename from receiver/src/request_handler_file_write.cpp rename to receiver/src/write_file_processor.cpp index f30341889e3ead8c301ae4b1a9f47dd4ea80ba26..0e92bfe0066c0e57d215642b9538df775c94b722 100644 --- a/receiver/src/request_handler_file_write.cpp +++ b/receiver/src/write_file_processor.cpp @@ -1,37 +1,34 @@ -#include "request_handler_file_write.h" +#include "write_file_processor.h" + #include "io/io_factory.h" +#include "receiver_error.h" +#include "preprocessor/definitions.h" #include "request.h" -#include "receiver_logger.h" #include "receiver_config.h" -#include "preprocessor/definitions.h" namespace asapo { -Error RequestHandlerFileWrite::ProcessRequest(Request* request) const { +WriteFileProcessor::WriteFileProcessor() : FileProcessor() { + +} + + +Error WriteFileProcessor::ProcessFile(const Request* request, bool overwrite) const { auto fsize = request->GetDataSize(); if (fsize <= 0) { return ReceiverErrorTemplates::kBadRequest.Generate("wrong file size"); } auto data = request->GetData(); - auto fname = request->GetFileName(); auto root_folder = request->GetFullPath(GetReceiverConfig()->root_folder); - auto err = io__->WriteDataToFile(root_folder, fname, (uint8_t*)data, (size_t) fsize, true); + + auto err = io__->WriteDataToFile(root_folder, fname, (uint8_t*)data, (size_t) fsize, true, overwrite); if (!err) { log__->Debug("saved file of size " + std::to_string(fsize) + " to " + root_folder + kPathSeparator + fname); } - return err; - -} -RequestHandlerFileWrite::RequestHandlerFileWrite() : io__{GenerateDefaultIO()} , log__{GetDefaultReceiverLogger()} { - -} - -StatisticEntity RequestHandlerFileWrite::GetStatisticEntity() const { - return StatisticEntity::kDisk; + return err; } - -} +} \ No newline at end of file diff --git a/receiver/src/write_file_processor.h b/receiver/src/write_file_processor.h new file mode 100644 index 0000000000000000000000000000000000000000..e946491fca68de5f03faefdef5af5e7e9bae57af --- /dev/null +++ b/receiver/src/write_file_processor.h @@ -0,0 +1,16 @@ +#ifndef ASAPO_WRITE_FILE_PROCESSOR_H +#define ASAPO_WRITE_FILE_PROCESSOR_H + +#include "file_processor.h" + +namespace asapo { + +class WriteFileProcessor final : public FileProcessor { + public: + WriteFileProcessor(); + Error ProcessFile(const Request* request, bool overwrite) const override; +}; + +} + +#endif //ASAPO_WRITE_FILE_PROCESSOR_H diff --git a/receiver/unittests/receiver_data_server/receiver_dataserver_mocking.h b/receiver/unittests/receiver_data_server/receiver_dataserver_mocking.h index 5c21f1e231cd076d77914dfb0e0bef8dfac94b10..6eacdb6c706bd8c47e13b757b2704930fee179ed 100644 --- a/receiver/unittests/receiver_data_server/receiver_dataserver_mocking.h +++ b/receiver/unittests/receiver_data_server/receiver_dataserver_mocking.h @@ -47,7 +47,7 @@ class MockPool : public RequestPool { Error AddRequests(GenericRequests requests) noexcept override { std::vector<GenericRequest> reqs; for (const auto& preq : requests) { - reqs.push_back(GenericRequest{preq->header}); + reqs.push_back(GenericRequest{preq->header, 0}); } return Error(AddRequests_t(std::move(reqs))); diff --git a/receiver/unittests/receiver_mocking.h b/receiver/unittests/receiver_mocking.h index a0ae6313589980e331cd373552fa183815c09f13..cce334542cfaf14f523d5730e812d46fe8673dfa 100644 --- a/receiver/unittests/receiver_mocking.h +++ b/receiver/unittests/receiver_mocking.h @@ -7,6 +7,7 @@ #include "../src/receiver_statistics.h" #include "../src/request.h" #include "../src/data_cache.h" +#include "../src/file_processor.h" namespace asapo { @@ -40,10 +41,28 @@ class MockStatistics : public asapo::ReceiverStatistics { }; +class MockHandlerDbCheckRequest : public asapo::RequestHandlerDbCheckRequest { + public: + MockHandlerDbCheckRequest(std::string collection_name_prefix): RequestHandlerDbCheckRequest(collection_name_prefix) {}; + + Error ProcessRequest(Request* request) const override { + return Error{ProcessRequest_t(*request)}; + } + + StatisticEntity GetStatisticEntity() const override { + return StatisticEntity::kDatabase; + } + + MOCK_CONST_METHOD1(ProcessRequest_t, ErrorInterface * (const Request& request)); + +}; + + class MockRequest: public Request { public: - MockRequest(const GenericRequestHeader& request_header, SocketDescriptor socket_fd, std::string origin_uri): - Request(request_header, socket_fd, std::move(origin_uri), nullptr) {}; + MockRequest(const GenericRequestHeader& request_header, SocketDescriptor socket_fd, std::string origin_uri, + const RequestHandlerDbCheckRequest* db_check_handler ): + Request(request_header, socket_fd, std::move(origin_uri), nullptr, db_check_handler) {}; MOCK_CONST_METHOD0(GetFileName, std::string()); MOCK_CONST_METHOD0(GetSubstream, std::string()); @@ -74,7 +93,17 @@ class MockRequest: public Request { MOCK_METHOD1(SetBeamtimeYear, void (std::string)); MOCK_CONST_METHOD1(GetFullPath, std::string (std::string)); + MOCK_CONST_METHOD0(WasAlreadyProcessed, bool()); + MOCK_METHOD0(SetAlreadyProcessedFlag, void()); + MOCK_METHOD1(SetWarningMessage, void(std::string)); + MOCK_CONST_METHOD0(GetWarningMessage, const std::string & ()); + + Error CheckForDuplicates() override { + return Error{CheckForDuplicates_t()}; + } + + MOCK_METHOD0(CheckForDuplicates_t, ErrorInterface * ()); }; @@ -99,6 +128,15 @@ class MockStatisticsSender: public StatisticsSender { }; +class MockFileProcessor: public FileProcessor { + public: + Error ProcessFile(const Request* request, bool overwrite) const override { + return Error{ProcessFile_t(request, overwrite)}; + + } + MOCK_CONST_METHOD2(ProcessFile_t, ErrorInterface * (const Request*, bool)); +}; + } #endif //ASAPO_RECEIVER_MOCKING_H diff --git a/receiver/unittests/test_connection.cpp b/receiver/unittests/test_connection.cpp index edacbcd6e98c558937fe808603bdcb8770617a57..844ccc76c889b2328ad90fdebe25de9a2d621400 100644 --- a/receiver/unittests/test_connection.cpp +++ b/receiver/unittests/test_connection.cpp @@ -120,7 +120,8 @@ class ConnectionTests : public Test { )); return nullptr; } else { - auto request = new Request(GenericRequestHeader{asapo::kOpcodeUnknownOp, 0, 1, 0, ""}, 0, connected_uri, nullptr); + auto request = new Request(GenericRequestHeader{asapo::kOpcodeUnknownOp, 0, 1, 0, ""}, 0, connected_uri, nullptr, + nullptr); EXPECT_CALL(mock_dispatcher, GetNextRequest_t(_)) .WillOnce(DoAll( SetArgPointee<0>(nullptr), diff --git a/receiver/unittests/test_request_handler_file_receive.cpp b/receiver/unittests/test_receive_file_processor.cpp similarity index 61% rename from receiver/unittests/test_request_handler_file_receive.cpp rename to receiver/unittests/test_receive_file_processor.cpp index 15d6931bf7138189ca4772e409d411d5b510a601..6a729431b4db5e67e58366b40169dbad54cb1645 100644 --- a/receiver/unittests/test_request_handler_file_receive.cpp +++ b/receiver/unittests/test_receive_file_processor.cpp @@ -4,13 +4,10 @@ #include "unittests/MockIO.h" #include "unittests/MockLogger.h" -#include "../src/receiver_error.h" -#include "../src/request.h" -#include "../src/request_handler.h" -#include "../src/request_handler_file_receive.h" +#include "../src/receive_file_processor.h" #include "common/networking.h" -#include "mock_receiver_config.h" #include "preprocessor/definitions.h" +#include "mock_receiver_config.h" #include "receiver_mocking.h" @@ -37,21 +34,22 @@ using ::asapo::FileDescriptor; using ::asapo::SocketDescriptor; using ::asapo::MockIO; using asapo::Request; -using asapo::RequestHandlerFileReceive; +using asapo::ReceiveFileProcessor; using ::asapo::GenericRequestHeader; using asapo::MockRequest; namespace { -TEST(FileReceive, Constructor) { - RequestHandlerFileReceive handler; - ASSERT_THAT(dynamic_cast<asapo::IO*>(handler.io__.get()), Ne(nullptr)); - ASSERT_THAT(dynamic_cast<const asapo::AbstractLogger*>(handler.log__), Ne(nullptr)); +TEST(ReceiveFileProcessor, Constructor) { + ReceiveFileProcessor processor; + ASSERT_THAT(dynamic_cast<asapo::IO*>(processor.io__.get()), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::AbstractLogger*>(processor.log__), Ne(nullptr)); + } -class FileReceiveHandlerTests : public Test { +class ReceiveFileProcessorTests : public Test { public: - RequestHandlerFileReceive handler; + ReceiveFileProcessor processor; NiceMock<MockIO> mock_io; std::unique_ptr<MockRequest> mock_request; NiceMock<asapo::MockLogger> mock_logger; @@ -59,54 +57,59 @@ class FileReceiveHandlerTests : public Test { std::string expected_file_name = "2"; std::string expected_beamtime_id = "beamtime_id"; std::string expected_beamline = "beamline"; - std::string expected_root_folder = "root_folder"; std::string expected_facility = "facility"; std::string expected_year = "2020"; + uint64_t expected_file_size = 10; + bool expected_overwrite = false; + std::string expected_root_folder = "root_folder"; std::string expected_full_path = expected_root_folder + asapo::kPathSeparator + expected_facility + asapo::kPathSeparator + "gpfs" + asapo::kPathSeparator + expected_beamline + asapo::kPathSeparator + expected_year + asapo::kPathSeparator + "data" + asapo::kPathSeparator + expected_beamtime_id; - - uint64_t expected_file_size = 10; + void ExpectFileWrite(const asapo::SimpleErrorTemplate* error_template); void MockRequestData(); void SetUp() override { GenericRequestHeader request_header; request_header.data_id = 2; - mock_request.reset(new MockRequest{request_header, expected_socket_id, ""}); - handler.io__ = std::unique_ptr<asapo::IO> {&mock_io}; - handler.log__ = &mock_logger; + asapo::ReceiverConfig test_config; + test_config.root_folder = expected_root_folder; + asapo::SetReceiverConfig(test_config, "none"); + processor.log__ = &mock_logger; + mock_request.reset(new MockRequest{request_header, 1, "", nullptr}); + processor.io__ = std::unique_ptr<asapo::IO> {&mock_io}; } void TearDown() override { - handler.io__.release(); + processor.io__.release(); } }; -TEST_F(FileReceiveHandlerTests, CheckStatisticEntity) { - auto entity = handler.GetStatisticEntity(); - ASSERT_THAT(entity, Eq(asapo::StatisticEntity::kDisk)); -} - -void FileReceiveHandlerTests::MockRequestData() { - EXPECT_CALL(*mock_request, GetDataSize()) - .WillOnce(Return(expected_file_size)) - ; +void ReceiveFileProcessorTests::MockRequestData() { EXPECT_CALL(*mock_request, GetSocket()) .WillOnce(Return(expected_socket_id)) ; - EXPECT_CALL(*mock_request, GetFullPath(expected_root_folder)) - .WillOnce(Return(expected_full_path)); + EXPECT_CALL(*mock_request, GetDataSize()).Times(1) + .WillRepeatedly(Return(expected_file_size)); - EXPECT_CALL(*mock_request, GetFileName()) - .WillOnce(Return(expected_file_name)) - ; + EXPECT_CALL(*mock_request, GetFullPath(expected_root_folder)).Times(1) + .WillRepeatedly(Return(expected_full_path)); + + EXPECT_CALL(*mock_request, GetFileName()).Times(1) + .WillRepeatedly(Return(expected_file_name)); } -TEST_F(FileReceiveHandlerTests, CallsReceiveFile) { +void ReceiveFileProcessorTests::ExpectFileWrite(const asapo::SimpleErrorTemplate* error_template) { + EXPECT_CALL(mock_io, WriteDataToFile_t(expected_full_path, expected_file_name, _, expected_file_size, true, + expected_overwrite)) + .WillOnce( + Return(error_template == nullptr ? nullptr : error_template->Generate().release())); +} + +TEST_F(ReceiveFileProcessorTests, CallsReceiveFile) { asapo::ReceiverConfig test_config; test_config.root_folder = expected_root_folder; @@ -115,22 +118,22 @@ TEST_F(FileReceiveHandlerTests, CallsReceiveFile) { MockRequestData(); EXPECT_CALL(mock_io, ReceiveDataToFile_t(expected_socket_id, expected_full_path, expected_file_name, expected_file_size, - true)) + true, expected_overwrite)) .WillOnce( Return(asapo::IOErrorTemplates::kUnknownIOError.Generate().release()) ); - auto err = handler.ProcessRequest(mock_request.get()); + auto err = processor.ProcessFile(mock_request.get(), expected_overwrite); ASSERT_THAT(err, Eq(asapo::IOErrorTemplates::kUnknownIOError)); } -TEST_F(FileReceiveHandlerTests, WritesToLog) { +TEST_F(ReceiveFileProcessorTests, WritesToLog) { MockRequestData(); - EXPECT_CALL(mock_io, ReceiveDataToFile_t(_, _, _, _, _)) + EXPECT_CALL(mock_io, ReceiveDataToFile_t(_, _, _, _, _, _)) .WillOnce(Return(nullptr)); EXPECT_CALL(mock_logger, Debug(AllOf(HasSubstr("received file"), @@ -140,8 +143,9 @@ TEST_F(FileReceiveHandlerTests, WritesToLog) { ) ) ); - handler.ProcessRequest(mock_request.get()); + processor.ProcessFile(mock_request.get(), expected_overwrite); } + } \ No newline at end of file diff --git a/receiver/unittests/test_request.cpp b/receiver/unittests/test_request.cpp index 392f83a13f7506a093c51a0521d0f1d22f7aee30..2a94be104d17590b7e95a522e46e710c219303fa 100644 --- a/receiver/unittests/test_request.cpp +++ b/receiver/unittests/test_request.cpp @@ -5,7 +5,7 @@ #include "../src/receiver_error.h" #include "../src/request.h" #include "../src/request_handler.h" -#include "../src/request_handler_file_write.h" +#include "../src/request_handler_file_process.h" #include "../src/request_handler_db_write.h" #include "database/database.h" @@ -60,6 +60,16 @@ class MockReqestHandler : public asapo::ReceiverRequestHandler { }; + + +TEST(RequestTest, Constructor) { + std::unique_ptr<Request> request; + GenericRequestHeader generic_request_header; + request.reset(new Request{generic_request_header, 1, "", nullptr, nullptr}); + ASSERT_THAT(request->WasAlreadyProcessed(), false); +} + + class RequestTests : public Test { public: GenericRequestHeader generic_request_header; @@ -86,7 +96,7 @@ class RequestTests : public Test { generic_request_header.op_code = expected_op_code; generic_request_header.custom_data[asapo::kPosIngestMode] = asapo::kDefaultIngestMode; strcpy(generic_request_header.message, expected_request_message); - request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr}); + request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr, nullptr}); request->io__ = std::unique_ptr<asapo::IO> {&mock_io}; ON_CALL(mock_io, Receive_t(expected_socket_id, _, data_size_, _)).WillByDefault( DoAll(SetArgPointee<3>(nullptr), @@ -166,7 +176,7 @@ void RequestTests::ExpectFileName(std::string sended, std::string received) { strcpy(generic_request_header.message, sended.c_str()); request->io__.release(); - request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr}); + request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr, nullptr}); request->io__ = std::unique_ptr<asapo::IO> {&mock_io};; auto fname = request->GetFileName(); @@ -180,7 +190,7 @@ TEST_F(RequestTests, GetSubstream) { strcpy(generic_request_header.substream, expected_substream.c_str()); request->io__.release(); - request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr}); + request.reset(new Request{generic_request_header, expected_socket_id, expected_origin_uri, nullptr, nullptr}); request->io__ = std::unique_ptr<asapo::IO> {&mock_io};; auto substream = request->GetSubstream(); @@ -261,6 +271,18 @@ TEST_F(RequestTests, RequestTests_GetFullPath) { ASSERT_THAT(request->GetFullPath("test_folder"), expected_path); } +TEST_F(RequestTests, SetGetWarningMessage) { + request->SetWarningMessage("warn"); + + ASSERT_THAT(request->GetWarningMessage(), "warn"); +} + + +TEST_F(RequestTests, SetGetOverwriteAllowed) { + request->SetAlreadyProcessedFlag(); + + ASSERT_THAT(request->WasAlreadyProcessed(), true); +} } diff --git a/receiver/unittests/test_request_factory.cpp b/receiver/unittests/test_request_factory.cpp index efd613811bfd66081c0979994c50580403a76cdc..223871137f603fe493e9c2f8fdbce111e9355f35 100644 --- a/receiver/unittests/test_request_factory.cpp +++ b/receiver/unittests/test_request_factory.cpp @@ -10,7 +10,7 @@ #include "../src/request.h" #include "../src/request_factory.h" #include "../src/request_handler.h" -#include "../src/request_handler_file_write.h" +#include "../src/request_handler_file_process.h" #include "../src/request_handler_db_write.h" #include "../src/request_handler_authorize.h" #include "../src/request_handler_receive_data.h" @@ -89,7 +89,7 @@ TEST_F(FactoryTests, ReturnsDataRequestOnkNetOpcodeSendDataCode) { ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerAuthorize*>(request->GetListHandlers()[0]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerReceiveMetaData*>(request->GetListHandlers()[1]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerReceiveData*>(request->GetListHandlers()[2]), Ne(nullptr)); - ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileWrite*>(request->GetListHandlers()[3]), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileProcess*>(request->GetListHandlers()[3]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerDbWrite*>(request->GetListHandlers().back()), Ne(nullptr)); } } @@ -108,7 +108,7 @@ TEST_F(FactoryTests, ReturnsDataRequestOnkNetOpcodeSendDataCodeLargeFile) { ASSERT_THAT(request->GetListHandlers().size(), Eq(4)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerAuthorize*>(request->GetListHandlers()[0]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerReceiveMetaData*>(request->GetListHandlers()[1]), Ne(nullptr)); - ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileReceive*>(request->GetListHandlers()[2]), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileProcess*>(request->GetListHandlers()[2]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerDbWrite*>(request->GetListHandlers().back()), Ne(nullptr)); } } @@ -156,7 +156,7 @@ TEST_F(FactoryTests, DoNotAddDbWriterIfNotWanted) { ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerAuthorize*>(request->GetListHandlers()[0]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerReceiveMetaData*>(request->GetListHandlers()[1]), Ne(nullptr)); ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerReceiveData*>(request->GetListHandlers()[2]), Ne(nullptr)); - ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileWrite*>(request->GetListHandlers()[3]), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::RequestHandlerFileProcess*>(request->GetListHandlers()[3]), Ne(nullptr)); } TEST_F(FactoryTests, CachePassedToRequest) { @@ -190,7 +190,7 @@ TEST_F(FactoryTests, DonNotGenerateMetadataRequestIfNoDbConfigured) { generic_request_header.op_code = asapo::Opcode::kOpcodeTransferMetaData; auto request = factory.GenerateRequest(generic_request_header, 1, origin_uri, &err); - ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kReject)); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kInternalServerError)); } @@ -205,7 +205,7 @@ TEST_F(FactoryTests, DonNotGenerateRequestIfWriteToDiskNotActive) { auto request = factory.GenerateRequest(generic_request_header, 1, origin_uri, &err); - ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kReject)); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kInternalServerError)); } TEST_F(FactoryTests, DonNotGenerateRequestIfIngestModeIsWrong) { diff --git a/receiver/unittests/test_request_handler_authorizer.cpp b/receiver/unittests/test_request_handler_authorizer.cpp index b80e4fe993d658de89da339a47ecb55f4e5b02f8..b6e7ff1b3f7b40df5fea2e54b1472978665e472a 100644 --- a/receiver/unittests/test_request_handler_authorizer.cpp +++ b/receiver/unittests/test_request_handler_authorizer.cpp @@ -78,7 +78,7 @@ class AuthorizerHandlerTests : public Test { void MockRequestData(); void SetUp() override { GenericRequestHeader request_header; - mock_request.reset(new MockRequest{request_header, 1, expected_producer_uri}); + mock_request.reset(new MockRequest{request_header, 1, expected_producer_uri, nullptr}); handler.http_client__ = std::unique_ptr<asapo::HttpClient> {&mock_http_client}; handler.log__ = &mock_logger; config.authorization_server = expected_authorization_server; diff --git a/receiver/unittests/test_request_handler_db.cpp b/receiver/unittests/test_request_handler_db.cpp index a2804fae725a1b50aa76db4221b951b57ec155b6..1f4dc691c5eb5570574a722020d45f0cca77519a 100644 --- a/receiver/unittests/test_request_handler_db.cpp +++ b/receiver/unittests/test_request_handler_db.cpp @@ -79,7 +79,7 @@ class DbHandlerTests : public Test { handler.db_client__ = std::unique_ptr<asapo::Database> {&mock_db}; handler.log__ = &mock_logger; handler.http_client__ = std::unique_ptr<asapo::HttpClient> {&mock_http_client}; - mock_request.reset(new NiceMock<MockRequest> {request_header, 1, ""}); + mock_request.reset(new NiceMock<MockRequest> {request_header, 1, "", nullptr}); ON_CALL(*mock_request, GetBeamtimeId()).WillByDefault(ReturnRef(expected_beamtime_id)); ON_CALL(*mock_request, GetStream()).WillByDefault(ReturnRef(expected_stream)); diff --git a/receiver/unittests/test_request_handler_db_check_request.cpp b/receiver/unittests/test_request_handler_db_check_request.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e40cbfbb4cfe1091924edbc317db2d71f6f06619 --- /dev/null +++ b/receiver/unittests/test_request_handler_db_check_request.cpp @@ -0,0 +1,273 @@ +#include <functional> + +#include <gtest/gtest.h> +#include <gmock/gmock.h> +#include <database/db_error.h> + +#include "unittests/MockIO.h" +#include "unittests/MockDatabase.h" +#include "unittests/MockLogger.h" + +#include "../src/receiver_error.h" +#include "../src/request.h" +#include "../src/request_factory.h" +#include "../src/request_handler.h" +#include "../src/request_handler_db_check_request.h" +#include "common/networking.h" +#include "../../common/cpp/src/database/mongodb_client.h" +#include "mock_receiver_config.h" +#include "common/data_structs.h" + +#include "receiver_mocking.h" + +using asapo::MockRequest; +using asapo::FileInfo; +using ::testing::Test; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::_; +using ::testing::DoAll; +using ::testing::SetArgReferee; +using ::testing::Gt; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::Mock; +using ::testing::NiceMock; +using ::testing::InSequence; +using ::testing::SetArgPointee; +using ::testing::AllOf; +using ::testing::HasSubstr; + + +using ::asapo::Error; +using ::asapo::ErrorInterface; +using ::asapo::FileDescriptor; +using ::asapo::SocketDescriptor; +using ::asapo::MockIO; +using asapo::Request; +using asapo::RequestHandlerDbCheckRequest; +using ::asapo::GenericRequestHeader; + +using asapo::MockDatabase; +using asapo::RequestFactory; +using asapo::SetReceiverConfig; +using asapo::ReceiverConfig; + +using MockFunctions = std::vector<std::function<void(asapo::ErrorInterface*, bool )>>; + +namespace { + +TEST(DbCheckRequestHandler, Constructor) { + RequestHandlerDbCheckRequest handler{""}; + ASSERT_THAT(dynamic_cast<asapo::HttpClient*>(handler.http_client__.get()), Ne(nullptr)); +} + + +class DbCheckRequestHandlerTests : public Test { + public: + std::string expected_substream = "substream"; + std::string expected_collection_name = std::string(asapo::kDBDataCollectionNamePrefix) + "_" + expected_substream; + RequestHandlerDbCheckRequest handler{asapo::kDBDataCollectionNamePrefix}; + std::unique_ptr<NiceMock<MockRequest>> mock_request; + NiceMock<MockDatabase> mock_db; + NiceMock<asapo::MockLogger> mock_logger; + ReceiverConfig config; + std::string expected_beamtime_id = "beamtime_id"; + std::string expected_default_stream = "detector"; + std::string expected_stream = "stream"; + std::string expected_host_ip = "127.0.0.1"; + uint64_t expected_port = 1234; + uint64_t expected_buf_id = 18446744073709551615ull; + std::string expected_file_name = "2"; + std::string expected_metadata = "meta"; + uint64_t expected_file_size = 10; + uint64_t expected_id = 15; + uint64_t expected_subset_id = 16; + uint64_t expected_subset_size = 2; + uint64_t expected_custom_data[asapo::kNCustomParams] {0, expected_subset_id, expected_subset_size}; + FileInfo expected_file_info; + MockFunctions mock_functions; + int n_run = 0; + void SetUp() override { + GenericRequestHeader request_header; + request_header.data_id = 2; + request_header.op_code = asapo::Opcode::kOpcodeTransferData; + handler.db_client__ = std::unique_ptr<asapo::Database> {&mock_db}; + handler.log__ = &mock_logger; + mock_request.reset(new NiceMock<MockRequest> {request_header, 1, "", nullptr}); + config.database_uri = "127.0.0.1:27017"; + config.advertise_ip = expected_host_ip; + config.dataserver.listen_port = expected_port; + SetReceiverConfig(config, "none"); + expected_file_info = PrepareFileInfo(); + mock_functions.push_back([this](asapo::ErrorInterface * error, bool expect_compare) { + MockGetByID(error, expect_compare); + n_run++; + }); + mock_functions.push_back([this](asapo::ErrorInterface * error, bool expect_compare) { + MockGetSetByID(error, expect_compare); + n_run++; + }); + + ON_CALL(*mock_request, GetBeamtimeId()).WillByDefault(ReturnRef(expected_beamtime_id)); + } + void ExpectRequestParams(asapo::Opcode op_code, const std::string& stream, bool expect_compare = true); + + FileInfo PrepareFileInfo(); + void MockGetByID(asapo::ErrorInterface* error, bool expect_compare); + void MockGetSetByID(asapo::ErrorInterface* error, bool expect_compare); + void TearDown() override { + handler.db_client__.release(); + } + + +}; + +MATCHER_P(CompareFileInfo, file, "") { + if (arg.size != file.size) return false; + if (arg.source != file.source) return false; + if (arg.buf_id != file.buf_id) return false; + if (arg.name != file.name) return false; + if (arg.id != file.id) return false; + if (arg.metadata != file.metadata) return false; + + return true; +} + + +void DbCheckRequestHandlerTests::ExpectRequestParams(asapo::Opcode op_code, const std::string& stream, + bool expect_compare) { + + std::string db_name = expected_beamtime_id; + db_name += "_" + stream; + + if (n_run == 0) { + EXPECT_CALL(mock_db, Connect_t(config.database_uri, db_name)). + WillOnce(testing::Return(nullptr)); + EXPECT_CALL(*mock_request, GetBeamtimeId()) + .WillOnce(ReturnRef(expected_beamtime_id)) + ; + + EXPECT_CALL(*mock_request, GetStream()) + .WillOnce(ReturnRef(stream)) + ; + } + + + if (expect_compare) { + EXPECT_CALL(*mock_request, GetDataSize()) + .WillOnce(Return(expected_file_size)) + ; + + EXPECT_CALL(*mock_request, GetFileName()) + .WillOnce(Return(expected_file_name)) + ; + + EXPECT_CALL(*mock_request, GetMetaData()) + .WillOnce(ReturnRef(expected_metadata)) + ; + } + + + EXPECT_CALL(*mock_request, GetSubstream()) + .WillOnce(Return(expected_substream)) + ; + + + + EXPECT_CALL(*mock_request, GetDataID()) + .WillOnce(Return(expected_id)) + ; + + EXPECT_CALL(*mock_request, GetOpCode()) + .WillOnce(Return(op_code)) + ; + + if (op_code == asapo::Opcode::kOpcodeTransferSubsetData) { + EXPECT_CALL(*mock_request, GetCustomData_t()) + .WillOnce(Return(expected_custom_data)) + ; + } +} + +FileInfo DbCheckRequestHandlerTests::PrepareFileInfo() { + FileInfo file_info; + file_info.size = expected_file_size; + file_info.name = expected_file_name; + file_info.id = expected_id; + file_info.buf_id = expected_buf_id; + file_info.source = expected_host_ip + ":" + std::to_string(expected_port); + file_info.metadata = expected_metadata; + return file_info; +} + +void DbCheckRequestHandlerTests::MockGetByID(asapo::ErrorInterface* error, bool expect_compare ) { + ExpectRequestParams(asapo::Opcode::kOpcodeTransferData, expected_stream, expect_compare); + EXPECT_CALL(mock_db, GetById_t(expected_collection_name, expected_id, _)). + WillOnce(DoAll( + SetArgPointee<2>(expected_file_info), + testing::Return(error) + )); +} + +void DbCheckRequestHandlerTests::MockGetSetByID(asapo::ErrorInterface* error, bool expect_compare ) { + ExpectRequestParams(asapo::Opcode::kOpcodeTransferSubsetData, expected_stream, expect_compare); + EXPECT_CALL(mock_db, GetSetById_t(expected_collection_name, expected_subset_id, expected_id, _)). + WillOnce(DoAll( + SetArgPointee<3>(expected_file_info), + testing::Return(error) + )); +} + + +TEST_F(DbCheckRequestHandlerTests, ErrorIfRecordsDoNotMatch) { + expected_file_info.metadata = expected_metadata + "_"; + + for (auto mock : mock_functions) { + mock(nullptr, true); + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kBadRequest)); + Mock::VerifyAndClearExpectations(mock_request.get()); + } + +} + +TEST_F(DbCheckRequestHandlerTests, DuplicateErrorIfRecordsMatch) { + for (auto mock : mock_functions) { + mock(nullptr, true); + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kWarningDuplicatedRequest)); + Mock::VerifyAndClearExpectations(mock_request.get()); + } +} + +TEST_F(DbCheckRequestHandlerTests, DuplicateErrorIfRecordsMatchWithEmptyMetadata) { + expected_file_info.metadata = "{}"; + expected_metadata = ""; + for (auto mock : mock_functions) { + mock(nullptr, true); + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kWarningDuplicatedRequest)); + Mock::VerifyAndClearExpectations(mock_request.get()); + } +} + +TEST_F(DbCheckRequestHandlerTests, OkIfNotFound) { + for (auto mock : mock_functions) { + mock(asapo::DBErrorTemplates::kNoRecord.Generate().release(), false); + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(nullptr)); + Mock::VerifyAndClearExpectations(mock_request.get()); + } +} + +TEST_F(DbCheckRequestHandlerTests, ErrorIfDbError) { + for (auto mock : mock_functions) { + mock(asapo::DBErrorTemplates::kConnectionError.Generate().release(), false); + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(asapo::DBErrorTemplates::kConnectionError)); + Mock::VerifyAndClearExpectations(mock_request.get()); + } +} + +} diff --git a/receiver/unittests/test_request_handler_db_meta_writer.cpp b/receiver/unittests/test_request_handler_db_meta_writer.cpp index 822bbc42a274d535ecfdca4e39646fcba65f8442..af3070b793ed57c8c7bab63add5cfa875bccf911 100644 --- a/receiver/unittests/test_request_handler_db_meta_writer.cpp +++ b/receiver/unittests/test_request_handler_db_meta_writer.cpp @@ -73,7 +73,7 @@ class DbMetaWriterHandlerTests : public Test { request_header.data_id = 0; handler.db_client__ = std::unique_ptr<asapo::Database> {&mock_db}; handler.log__ = &mock_logger; - mock_request.reset(new NiceMock<MockRequest> {request_header, 1, ""}); + mock_request.reset(new NiceMock<MockRequest> {request_header, 1, "", nullptr}); ON_CALL(*mock_request, GetBeamtimeId()).WillByDefault(ReturnRef(expected_beamtime_id)); } void TearDown() override { diff --git a/receiver/unittests/test_request_handler_db_writer.cpp b/receiver/unittests/test_request_handler_db_writer.cpp index 8c6454049755359544d211b2e191b9e114ffffd0..4f82656c72d3a2b067cc171f85182f1cd4be7bc7 100644 --- a/receiver/unittests/test_request_handler_db_writer.cpp +++ b/receiver/unittests/test_request_handler_db_writer.cpp @@ -1,5 +1,6 @@ #include <gtest/gtest.h> #include <gmock/gmock.h> +#include <database/db_error.h> #include "unittests/MockIO.h" #include "unittests/MockDatabase.h" @@ -34,6 +35,7 @@ using ::testing::InSequence; using ::testing::SetArgPointee; using ::testing::AllOf; using ::testing::HasSubstr; +using ::testing::AtLeast; using ::asapo::Error; @@ -82,13 +84,15 @@ class DbWriterHandlerTests : public Test { uint64_t expected_subset_id = 15; uint64_t expected_subset_size = 2; uint64_t expected_custom_data[asapo::kNCustomParams] {0, expected_subset_id, expected_subset_size}; + asapo::MockHandlerDbCheckRequest mock_db_check_handler{asapo::kDBDataCollectionNamePrefix}; + void SetUp() override { GenericRequestHeader request_header; request_header.data_id = 2; request_header.op_code = asapo::Opcode::kOpcodeTransferData; handler.db_client__ = std::unique_ptr<asapo::Database> {&mock_db}; handler.log__ = &mock_logger; - mock_request.reset(new NiceMock<MockRequest> {request_header, 1, ""}); + mock_request.reset(new NiceMock<MockRequest> {request_header, 1, "", &mock_db_check_handler}); config.database_uri = "127.0.0.1:27017"; config.advertise_ip = expected_host_ip; config.dataserver.listen_port = expected_port; @@ -97,7 +101,8 @@ class DbWriterHandlerTests : public Test { ON_CALL(*mock_request, GetBeamtimeId()).WillByDefault(ReturnRef(expected_beamtime_id)); } void ExpectRequestParams(asapo::Opcode op_code, const std::string& stream); - + void ExpectLogger(); + void ExpectDuplicatedID(); FileInfo PrepareFileInfo(); void TearDown() override { handler.db_client__.release(); @@ -119,6 +124,11 @@ MATCHER_P(CompareFileInfo, file, "") { void DbWriterHandlerTests::ExpectRequestParams(asapo::Opcode op_code, const std::string& stream) { + + EXPECT_CALL(*mock_request, WasAlreadyProcessed()) + .WillOnce(Return(false)) + ; + EXPECT_CALL(*mock_request, GetBeamtimeId()) .WillOnce(ReturnRef(expected_beamtime_id)) ; @@ -155,8 +165,8 @@ void DbWriterHandlerTests::ExpectRequestParams(asapo::Opcode op_code, const std: .WillOnce(ReturnRef(expected_metadata)) ; - EXPECT_CALL(*mock_request, GetDataID()) - .WillOnce(Return(expected_id)) + EXPECT_CALL(*mock_request, GetDataID()).Times(AtLeast(1)).WillRepeatedly + (Return(expected_id)) ; EXPECT_CALL(*mock_request, GetOpCode()) @@ -183,15 +193,7 @@ FileInfo DbWriterHandlerTests::PrepareFileInfo() { file_info.metadata = expected_metadata; return file_info; } - -TEST_F(DbWriterHandlerTests, CallsInsert) { - - ExpectRequestParams(asapo::Opcode::kOpcodeTransferData, expected_stream); - auto file_info = PrepareFileInfo(); - - EXPECT_CALL(mock_db, Insert_t(expected_collection_name, CompareFileInfo(file_info), _)). - WillOnce(testing::Return(nullptr)); - +void DbWriterHandlerTests::ExpectLogger() { EXPECT_CALL(mock_logger, Debug(AllOf(HasSubstr("insert record"), HasSubstr(config.database_uri), HasSubstr(expected_beamtime_id), @@ -201,6 +203,17 @@ TEST_F(DbWriterHandlerTests, CallsInsert) { ) ); +} + +TEST_F(DbWriterHandlerTests, CallsInsert) { + + ExpectRequestParams(asapo::Opcode::kOpcodeTransferData, expected_stream); + auto file_info = PrepareFileInfo(); + + EXPECT_CALL(mock_db, Insert_t(expected_collection_name, CompareFileInfo(file_info), false)). + WillOnce(testing::Return(nullptr)); + ExpectLogger(); + handler.ProcessRequest(mock_request.get()); } @@ -211,19 +224,61 @@ TEST_F(DbWriterHandlerTests, CallsInsertSubset) { EXPECT_CALL(mock_db, InsertAsSubset_t(expected_collection_name, CompareFileInfo(file_info), expected_subset_id, - expected_subset_size, _)). + expected_subset_size, false)). WillOnce(testing::Return(nullptr)); - - EXPECT_CALL(mock_logger, Debug(AllOf(HasSubstr("insert record"), - HasSubstr(config.database_uri), - HasSubstr(expected_beamtime_id), - HasSubstr(expected_collection_name) - ) - ) - ); + ExpectLogger(); handler.ProcessRequest(mock_request.get()); } +void DbWriterHandlerTests::ExpectDuplicatedID() { + ExpectRequestParams(asapo::Opcode::kOpcodeTransferData, expected_stream); + auto file_info = PrepareFileInfo(); + + EXPECT_CALL(mock_db, Insert_t(expected_collection_name, CompareFileInfo(file_info), false)). + WillOnce(testing::Return(asapo::DBErrorTemplates::kDuplicateID.Generate().release())); +} + +TEST_F(DbWriterHandlerTests, SkipIfWasAlreadyProcessed) { + EXPECT_CALL(*mock_request, WasAlreadyProcessed()) + .WillOnce(Return(true)); + + EXPECT_CALL(*mock_request, GetBeamtimeId()).Times(0); + + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(nullptr)); + +} + +TEST_F(DbWriterHandlerTests, DuplicatedRequest_SameRecord) { + ExpectDuplicatedID(); + + EXPECT_CALL(*mock_request, SetWarningMessage(HasSubstr("duplicate record"))); + EXPECT_CALL(*mock_request, CheckForDuplicates_t()) + .WillOnce( + Return(asapo::ReceiverErrorTemplates::kWarningDuplicatedRequest.Generate().release()) + ); + + EXPECT_CALL(mock_logger, Warning(HasSubstr("ignoring"))); + + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(nullptr)); + +} + +TEST_F(DbWriterHandlerTests, DuplicatedRequest_DifferentRecord) { + ExpectDuplicatedID(); + + EXPECT_CALL(*mock_request, CheckForDuplicates_t()) + .WillOnce( + Return(asapo::ReceiverErrorTemplates::kBadRequest.Generate().release()) + ); + + auto err = handler.ProcessRequest(mock_request.get()); + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kBadRequest)); + +} + + } \ No newline at end of file diff --git a/receiver/unittests/test_request_handler_file_process.cpp b/receiver/unittests/test_request_handler_file_process.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0978d571e371ff62573c82e314e610a89097de0d --- /dev/null +++ b/receiver/unittests/test_request_handler_file_process.cpp @@ -0,0 +1,136 @@ +#include <gtest/gtest.h> +#include <gmock/gmock.h> + +#include "unittests/MockIO.h" +#include "unittests/MockLogger.h" + +#include "../src/receiver_error.h" +#include "../src/request.h" +#include "../src/request_handler.h" +#include "../src/request_handler_file_process.h" +#include "common/networking.h" +#include "mock_receiver_config.h" +#include "preprocessor/definitions.h" + +#include "receiver_mocking.h" + +using ::testing::Test; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::_; +using ::testing::DoAll; +using ::testing::SetArgReferee; +using ::testing::Gt; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::Mock; +using ::testing::NiceMock; +using ::testing::InSequence; +using ::testing::SetArgPointee; +using ::testing::AllOf; +using ::testing::HasSubstr; + + +using ::asapo::Error; +using ::asapo::ErrorInterface; +using ::asapo::FileDescriptor; +using ::asapo::SocketDescriptor; +using ::asapo::MockIO; +using asapo::Request; +using asapo::RequestHandlerFileProcess; +using ::asapo::GenericRequestHeader; +using asapo::MockRequest; + +namespace { + +TEST(FileWrite, Constructor) { + RequestHandlerFileProcess handler(nullptr); + ASSERT_THAT(dynamic_cast<asapo::IO*>(handler.io__.get()), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::AbstractLogger*>(handler.log__), Ne(nullptr)); +} + +class FileWriteHandlerTests : public Test { + public: + asapo::MockFileProcessor mock_file_processor; + RequestHandlerFileProcess handler{&mock_file_processor}; + NiceMock<MockIO> mock_io; + std::unique_ptr<MockRequest> mock_request; + NiceMock<asapo::MockLogger> mock_logger; + void ExpecFileProcess(const asapo::SimpleErrorTemplate* error_template, bool overwrite); + void SetUp() override { + GenericRequestHeader request_header; + mock_request.reset(new MockRequest{request_header, 1, "", nullptr}); + handler.io__ = std::unique_ptr<asapo::IO> {&mock_io}; + handler.log__ = &mock_logger; + } + void TearDown() override { + handler.io__.release(); + } + +}; + +TEST_F(FileWriteHandlerTests, CheckStatisticEntity) { + auto entity = handler.GetStatisticEntity(); + ASSERT_THAT(entity, Eq(asapo::StatisticEntity::kDisk)); +} + +void FileWriteHandlerTests::ExpecFileProcess(const asapo::SimpleErrorTemplate* error_template, bool overwrite) { + EXPECT_CALL(mock_file_processor, ProcessFile_t(mock_request.get(), overwrite)) + .WillOnce( + Return(error_template == nullptr ? nullptr : error_template->Generate().release())); +} + +TEST_F(FileWriteHandlerTests, FileAlreadyExists_NoRecordInDb) { + EXPECT_CALL(*mock_request, SetWarningMessage(HasSubstr("overwritten"))); + EXPECT_CALL(*mock_request, CheckForDuplicates_t()) + .WillOnce( + Return(nullptr) + ); + + EXPECT_CALL(mock_logger, Warning(HasSubstr("overwriting"))); + + ExpecFileProcess(&asapo::IOErrorTemplates::kFileAlreadyExists, false); + ExpecFileProcess(nullptr, true); + + auto err = handler.ProcessRequest(mock_request.get()); + + ASSERT_THAT(err, Eq(nullptr)); +} + +TEST_F(FileWriteHandlerTests, FileAlreadyExists_DuplicatedRecordInDb) { + + EXPECT_CALL(*mock_request, SetWarningMessage(HasSubstr("ignore"))); + EXPECT_CALL(*mock_request, SetAlreadyProcessedFlag()); + EXPECT_CALL(mock_logger, Warning(HasSubstr("duplicated"))); + EXPECT_CALL(*mock_request, GetDataID()).WillOnce(Return(1)); + + ExpecFileProcess(&asapo::IOErrorTemplates::kFileAlreadyExists, false); + + EXPECT_CALL(*mock_request, CheckForDuplicates_t()) + .WillOnce( + Return(asapo::ReceiverErrorTemplates::kWarningDuplicatedRequest.Generate().release()) + ); + + auto err = handler.ProcessRequest(mock_request.get()); + + ASSERT_THAT(err, Eq(nullptr)); +} + +TEST_F(FileWriteHandlerTests, FileAlreadyExists_DifferentRecordInDb) { + + ExpecFileProcess(&asapo::IOErrorTemplates::kFileAlreadyExists, false); + + EXPECT_CALL(*mock_request, CheckForDuplicates_t()) + .WillOnce( + Return(asapo::ReceiverErrorTemplates::kBadRequest.Generate().release()) + ); + + + auto err = handler.ProcessRequest(mock_request.get()); + + ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kBadRequest)); +} + + + +} \ No newline at end of file diff --git a/receiver/unittests/test_request_handler_receive_data.cpp b/receiver/unittests/test_request_handler_receive_data.cpp index 1b852b766b20963013adbecc58db1ecd84970871..9991f5c532b4cf0ec0c9e110e4bb9ee9133173b8 100644 --- a/receiver/unittests/test_request_handler_receive_data.cpp +++ b/receiver/unittests/test_request_handler_receive_data.cpp @@ -75,7 +75,7 @@ class ReceiveDataHandlerTests : public Test { generic_request_header.op_code = expected_op_code; generic_request_header.custom_data[asapo::kPosIngestMode] = asapo::kDefaultIngestMode; strcpy(generic_request_header.message, expected_request_message); - request.reset(new Request{generic_request_header, socket_fd_, expected_origin_uri, nullptr}); + request.reset(new Request{generic_request_header, socket_fd_, expected_origin_uri, nullptr, nullptr}); handler.io__ = std::unique_ptr<asapo::IO> {&mock_io}; handler.log__ = &mock_logger; } @@ -114,7 +114,7 @@ TEST_F(ReceiveDataHandlerTests, CheckStatisticEntity) { TEST_F(ReceiveDataHandlerTests, HandleDoesNotReceiveEmptyData) { generic_request_header.data_size = 0; - request.reset(new Request{generic_request_header, socket_fd_, "", nullptr}); + request.reset(new Request{generic_request_header, socket_fd_, "", nullptr, nullptr}); EXPECT_CALL(mock_io, Receive_t(_, _, _, _)).Times(0); @@ -127,7 +127,7 @@ TEST_F(ReceiveDataHandlerTests, HandleDoesNotReceiveEmptyData) { TEST_F(ReceiveDataHandlerTests, HandleDoesNotReceiveDataWhenMetadataOnlyWasSent) { generic_request_header.data_size = 10; generic_request_header.custom_data[asapo::kPosIngestMode] = asapo::kTransferMetaDataOnly; - request.reset(new Request{generic_request_header, socket_fd_, "", nullptr}); + request.reset(new Request{generic_request_header, socket_fd_, "", nullptr, nullptr}); auto err = handler.ProcessRequest(request.get()); diff --git a/receiver/unittests/test_request_handler_receive_metadata.cpp b/receiver/unittests/test_request_handler_receive_metadata.cpp index 92d526504854c5874ccebd1660e3d46e448c88a1..029d586c670b5327082e5264f2985a5fe6908120 100644 --- a/receiver/unittests/test_request_handler_receive_metadata.cpp +++ b/receiver/unittests/test_request_handler_receive_metadata.cpp @@ -73,7 +73,7 @@ class ReceiveMetaDataHandlerTests : public Test { generic_request_header.meta_size = expected_metadata_size; generic_request_header.op_code = expected_op_code; generic_request_header.custom_data[asapo::kPosIngestMode] = asapo::kDefaultIngestMode; - request.reset(new Request{generic_request_header, socket_fd_, expected_origin_uri, nullptr}); + request.reset(new Request{generic_request_header, socket_fd_, expected_origin_uri, nullptr, nullptr}); handler.io__ = std::unique_ptr<asapo::IO> {&mock_io}; handler.log__ = &mock_logger; } diff --git a/receiver/unittests/test_requests_dispatcher.cpp b/receiver/unittests/test_requests_dispatcher.cpp index 78b495f6799ee67d7c07ec426e119a2370012e29..12bcc973aae1b599c1b9ca3a0887e0ef267d50b7 100644 --- a/receiver/unittests/test_requests_dispatcher.cpp +++ b/receiver/unittests/test_requests_dispatcher.cpp @@ -65,7 +65,7 @@ TEST(RequestDispatcher, Constructor) { class MockRequest: public Request { public: MockRequest(const GenericRequestHeader& request_header, SocketDescriptor socket_fd): - Request(request_header, socket_fd, "", nullptr) {}; + Request(request_header, socket_fd, "", nullptr, nullptr) {}; Error Handle(ReceiverStatistics* statistics) override { return Error{Handle_t()}; }; @@ -259,15 +259,16 @@ TEST_F(RequestsDispatcherTests, OkProcessRequestSendOK) { } -TEST_F(RequestsDispatcherTests, ProcessRequestReturnsAlreadyExist) { - MockHandleRequest(true, asapo::IOErrorTemplates::kFileAlreadyExists.Generate()); +TEST_F(RequestsDispatcherTests, ProcessRequestReturnsOkWithWarning) { + MockHandleRequest(false); MockSendResponse(&response, false); + request->SetWarningMessage("duplicate"); auto err = dispatcher->ProcessRequest(request); - ASSERT_THAT(err, Eq(asapo::IOErrorTemplates::kFileAlreadyExists)); - ASSERT_THAT(response.error_code, Eq(asapo::kNetErrorFileIdAlreadyInUse)); - ASSERT_THAT(std::string(response.message), HasSubstr(std::string("kFileAlreadyExists"))); + ASSERT_THAT(err, Eq(nullptr)); + ASSERT_THAT(response.error_code, Eq(asapo::kNetErrorWarning)); + ASSERT_THAT(std::string(response.message), HasSubstr(std::string("duplicate"))); } TEST_F(RequestsDispatcherTests, ProcessRequestReturnsAuthorizationFailure) { diff --git a/receiver/unittests/test_request_handler_file_write.cpp b/receiver/unittests/test_write_file_processor.cpp similarity index 58% rename from receiver/unittests/test_request_handler_file_write.cpp rename to receiver/unittests/test_write_file_processor.cpp index 7a290e771bed91cbd9443ea79da6d16213859594..592de637d5ff1fbf03d0ad0e9df16951130075e0 100644 --- a/receiver/unittests/test_request_handler_file_write.cpp +++ b/receiver/unittests/test_write_file_processor.cpp @@ -4,13 +4,10 @@ #include "unittests/MockIO.h" #include "unittests/MockLogger.h" -#include "../src/receiver_error.h" -#include "../src/request.h" -#include "../src/request_handler.h" -#include "../src/request_handler_file_write.h" +#include "../src/write_file_processor.h" #include "common/networking.h" -#include "mock_receiver_config.h" #include "preprocessor/definitions.h" +#include "mock_receiver_config.h" #include "receiver_mocking.h" @@ -37,21 +34,22 @@ using ::asapo::FileDescriptor; using ::asapo::SocketDescriptor; using ::asapo::MockIO; using asapo::Request; -using asapo::RequestHandlerFileWrite; +using asapo::WriteFileProcessor; using ::asapo::GenericRequestHeader; using asapo::MockRequest; namespace { -TEST(FileWrite, Constructor) { - RequestHandlerFileWrite handler; - ASSERT_THAT(dynamic_cast<asapo::IO*>(handler.io__.get()), Ne(nullptr)); - ASSERT_THAT(dynamic_cast<const asapo::AbstractLogger*>(handler.log__), Ne(nullptr)); +TEST(WriteFileProcessor, Constructor) { + WriteFileProcessor processor; + ASSERT_THAT(dynamic_cast<asapo::IO*>(processor.io__.get()), Ne(nullptr)); + ASSERT_THAT(dynamic_cast<const asapo::AbstractLogger*>(processor.log__), Ne(nullptr)); + } -class FileWriteHandlerTests : public Test { +class WriteFileProcessorTests : public Test { public: - RequestHandlerFileWrite handler; + WriteFileProcessor processor; NiceMock<MockIO> mock_io; std::unique_ptr<MockRequest> mock_request; NiceMock<asapo::MockLogger> mock_logger; @@ -61,6 +59,7 @@ class FileWriteHandlerTests : public Test { std::string expected_facility = "facility"; std::string expected_year = "2020"; uint64_t expected_file_size = 10; + bool expected_overwrite = false; std::string expected_root_folder = "root_folder"; std::string expected_full_path = expected_root_folder + asapo::kPathSeparator + expected_facility + asapo::kPathSeparator + "gpfs" + @@ -68,72 +67,69 @@ class FileWriteHandlerTests : public Test { asapo::kPathSeparator + expected_year + asapo::kPathSeparator + "data" + asapo::kPathSeparator + expected_beamtime_id; - void MockRequestData(); + void ExpectFileWrite(const asapo::SimpleErrorTemplate* error_template); + void MockRequestData(int times = 1); void SetUp() override { GenericRequestHeader request_header; request_header.data_id = 2; - mock_request.reset(new MockRequest{request_header, 1, ""}); - handler.io__ = std::unique_ptr<asapo::IO> {&mock_io}; - handler.log__ = &mock_logger; + asapo::ReceiverConfig test_config; + test_config.root_folder = expected_root_folder; + asapo::SetReceiverConfig(test_config, "none"); + processor.log__ = &mock_logger; + mock_request.reset(new MockRequest{request_header, 1, "", nullptr}); + processor.io__ = std::unique_ptr<asapo::IO> {&mock_io}; } void TearDown() override { - handler.io__.release(); + processor.io__.release(); } }; -TEST_F(FileWriteHandlerTests, CheckStatisticEntity) { - auto entity = handler.GetStatisticEntity(); - ASSERT_THAT(entity, Eq(asapo::StatisticEntity::kDisk)); -} - -TEST_F(FileWriteHandlerTests, ErrorWhenZeroFileSize) { +TEST_F(WriteFileProcessorTests, ErrorWhenZeroFileSize) { EXPECT_CALL(*mock_request, GetDataSize()) .WillOnce(Return(0)); - auto err = handler.ProcessRequest(mock_request.get()); + auto err = processor.ProcessFile(mock_request.get(), false); ASSERT_THAT(err, Eq(asapo::ReceiverErrorTemplates::kBadRequest)); } -void FileWriteHandlerTests::MockRequestData() { - EXPECT_CALL(*mock_request, GetDataSize()) - .WillOnce(Return(expected_file_size)); +void WriteFileProcessorTests::MockRequestData(int times) { + EXPECT_CALL(*mock_request, GetDataSize()).Times(times) + .WillRepeatedly(Return(expected_file_size)); - EXPECT_CALL(*mock_request, GetData()) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*mock_request, GetData()).Times(times) + .WillRepeatedly(Return(nullptr)); - EXPECT_CALL(*mock_request, GetFullPath(expected_root_folder)) - .WillOnce(Return(expected_full_path)); + EXPECT_CALL(*mock_request, GetFullPath(expected_root_folder)).Times(times) + .WillRepeatedly(Return(expected_full_path)); - EXPECT_CALL(*mock_request, GetFileName()) - .WillOnce(Return(expected_file_name)); + EXPECT_CALL(*mock_request, GetFileName()).Times(times) + .WillRepeatedly(Return(expected_file_name)); } -TEST_F(FileWriteHandlerTests, CallsWriteFile) { - asapo::ReceiverConfig test_config; - test_config.root_folder = expected_root_folder; - - asapo::SetReceiverConfig(test_config, "none"); +void WriteFileProcessorTests::ExpectFileWrite(const asapo::SimpleErrorTemplate* error_template) { + EXPECT_CALL(mock_io, WriteDataToFile_t(expected_full_path, expected_file_name, _, expected_file_size, true, + expected_overwrite)) + .WillOnce( + Return(error_template == nullptr ? nullptr : error_template->Generate().release())); +} +TEST_F(WriteFileProcessorTests, CallsWriteFile) { MockRequestData(); - EXPECT_CALL(mock_io, WriteDataToFile_t(expected_full_path, expected_file_name, _, expected_file_size, true)) - .WillOnce( - Return(asapo::IOErrorTemplates::kUnknownIOError.Generate().release()) - ); + ExpectFileWrite(&asapo::IOErrorTemplates::kUnknownIOError); - auto err = handler.ProcessRequest(mock_request.get()); + auto err = processor.ProcessFile(mock_request.get(), expected_overwrite); ASSERT_THAT(err, Eq(asapo::IOErrorTemplates::kUnknownIOError)); } -TEST_F(FileWriteHandlerTests, WritesToLog) { +TEST_F(WriteFileProcessorTests, WritesToLog) { MockRequestData(); - EXPECT_CALL(mock_io, WriteDataToFile_t(_, _, _, _, _)) - .WillOnce(Return(nullptr)); + ExpectFileWrite(nullptr); EXPECT_CALL(mock_logger, Debug(AllOf(HasSubstr("saved file"), HasSubstr(expected_file_name), @@ -144,7 +140,11 @@ TEST_F(FileWriteHandlerTests, WritesToLog) { ) ) ); - handler.ProcessRequest(mock_request.get()); + auto err = processor.ProcessFile(mock_request.get(), expected_overwrite); + ASSERT_THAT(err, Eq(nullptr)); } + + + } \ No newline at end of file diff --git a/tests/automatic/bug_fixes/error-sending-data-using-callback-method/bugfix_callback.py b/tests/automatic/bug_fixes/error-sending-data-using-callback-method/bugfix_callback.py index 5b8d930cb429495415e46bf883cb922d8d17e9ed..8f8864647e2c59cc86b4b7a24b7d25b25a30ad29 100644 --- a/tests/automatic/bug_fixes/error-sending-data-using-callback-method/bugfix_callback.py +++ b/tests/automatic/bug_fixes/error-sending-data-using-callback-method/bugfix_callback.py @@ -25,7 +25,7 @@ class AsapoSender: def _callback(self, header, err): print ("hello self callback") -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads, 600) producer.set_log_level("debug") sender = AsapoSender(producer) diff --git a/tests/automatic/full_chain/send_recv_substreams/send_recv_substreams.cpp b/tests/automatic/full_chain/send_recv_substreams/send_recv_substreams.cpp index fb00a99973a6664deb2e77441489379330c8517c..d4eb41a5024faba5da17adf2480152eac1550d76 100644 --- a/tests/automatic/full_chain/send_recv_substreams/send_recv_substreams.cpp +++ b/tests/automatic/full_chain/send_recv_substreams/send_recv_substreams.cpp @@ -56,7 +56,7 @@ ProducerPtr CreateProducer(const Args& args) { asapo::Error err; auto producer = asapo::Producer::Create(args.server, 1, asapo::RequestHandlerType::kTcp, - asapo::SourceCredentials{args.beamtime_id, "", args.token }, &err); + asapo::SourceCredentials{args.beamtime_id, "", args.token }, 60, &err); if(err) { std::cerr << "Cannot start producer. ProducerError: " << err << std::endl; exit(EXIT_FAILURE); diff --git a/tests/automatic/full_chain/send_recv_substreams_python/send_recv_substreams.py b/tests/automatic/full_chain/send_recv_substreams_python/send_recv_substreams.py index a8081ed28c697db817aad91f6a82c7869b3093ff..f7c959370bdc4f68d6f5512af9fba4c65761bb63 100644 --- a/tests/automatic/full_chain/send_recv_substreams_python/send_recv_substreams.py +++ b/tests/automatic/full_chain/send_recv_substreams_python/send_recv_substreams.py @@ -28,7 +28,7 @@ def callback(header,err): source, beamtime, token = sys.argv[1:] broker = asapo_consumer.create_server_broker(source,".", beamtime,"",token,timeout) -producer = asapo_producer.create_producer(source,beamtime, "", token, 1) +producer = asapo_producer.create_producer(source,beamtime, "", token, 1, 600) producer.set_log_level("debug") group_id = broker.generate_group_id() diff --git a/tests/automatic/mongo_db/CMakeLists.txt b/tests/automatic/mongo_db/CMakeLists.txt index 1fcf5373dcf5c90af3342626386ed5d01f662928..e024474149ced3beb3179fa80d60a1c13a89891f 100644 --- a/tests/automatic/mongo_db/CMakeLists.txt +++ b/tests/automatic/mongo_db/CMakeLists.txt @@ -1,7 +1,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.7) # needed for fixtures add_subdirectory(connect) -add_subdirectory(insert) -add_subdirectory(insert_dataset) +add_subdirectory(insert_retrieve) +add_subdirectory(insert_retrieve_dataset) add_subdirectory(upsert) diff --git a/tests/automatic/mongo_db/insert/CMakeLists.txt b/tests/automatic/mongo_db/insert_retrieve/CMakeLists.txt similarity index 88% rename from tests/automatic/mongo_db/insert/CMakeLists.txt rename to tests/automatic/mongo_db/insert_retrieve/CMakeLists.txt index ed386c91e1331dc62c12843c6db8bbb4a17162ac..d1eef84c9f19c8dbf8f9335bc9b9545ae7483137 100644 --- a/tests/automatic/mongo_db/insert/CMakeLists.txt +++ b/tests/automatic/mongo_db/insert_retrieve/CMakeLists.txt @@ -1,5 +1,5 @@ -set(TARGET_NAME insert_mongodb) -set(SOURCE_FILES insert_mongodb.cpp) +set(TARGET_NAME insert_retrieve_mongodb) +set(SOURCE_FILES insert_retrieve_mongodb.cpp) ################################ diff --git a/tests/automatic/mongo_db/insert/cleanup_linux.sh b/tests/automatic/mongo_db/insert_retrieve/cleanup_linux.sh similarity index 100% rename from tests/automatic/mongo_db/insert/cleanup_linux.sh rename to tests/automatic/mongo_db/insert_retrieve/cleanup_linux.sh diff --git a/tests/automatic/mongo_db/insert/cleanup_windows.bat b/tests/automatic/mongo_db/insert_retrieve/cleanup_windows.bat similarity index 100% rename from tests/automatic/mongo_db/insert/cleanup_windows.bat rename to tests/automatic/mongo_db/insert_retrieve/cleanup_windows.bat diff --git a/tests/automatic/mongo_db/insert/insert_mongodb.cpp b/tests/automatic/mongo_db/insert_retrieve/insert_retrieve_mongodb.cpp similarity index 75% rename from tests/automatic/mongo_db/insert/insert_mongodb.cpp rename to tests/automatic/mongo_db/insert_retrieve/insert_retrieve_mongodb.cpp index 87a525270e9c8cd8b022ded7db3224212d2dcbc1..abeb042ebada81c563aa8d89579c4285bad5856c 100644 --- a/tests/automatic/mongo_db/insert/insert_mongodb.cpp +++ b/tests/automatic/mongo_db/insert_retrieve/insert_retrieve_mongodb.cpp @@ -6,9 +6,11 @@ using asapo::M_AssertContains; +using asapo::M_AssertTrue; using asapo::Error; + void Assert(const Error& error, const std::string& expect) { std::string result; if (error == nullptr) { @@ -58,5 +60,16 @@ int main(int argc, char* argv[]) { Assert(err, args.keyword); + if (args.keyword == "OK") { // check retrieve + asapo::FileInfo fi_db; + asapo::MongoDBClient db_new; + db_new.Connect("127.0.0.1", "data"); + err = db_new.GetById("test", fi.id, &fi_db); + M_AssertTrue(fi_db == fi, "get record from db"); + Assert(err, "OK"); + err = db_new.GetById("test", 0, &fi_db); + Assert(err, "No record"); + } + return 0; } diff --git a/tests/automatic/mongo_db/insert_dataset/CMakeLists.txt b/tests/automatic/mongo_db/insert_retrieve_dataset/CMakeLists.txt similarity index 84% rename from tests/automatic/mongo_db/insert_dataset/CMakeLists.txt rename to tests/automatic/mongo_db/insert_retrieve_dataset/CMakeLists.txt index a7c1d9648131f55d067d570fb6eca268641cc145..d4c09f19733a86ed3d81c341663a9f3f0cfcfac3 100644 --- a/tests/automatic/mongo_db/insert_dataset/CMakeLists.txt +++ b/tests/automatic/mongo_db/insert_retrieve_dataset/CMakeLists.txt @@ -1,5 +1,5 @@ -set(TARGET_NAME insert_dataset_mongodb) -set(SOURCE_FILES insert_dataset_mongodb.cpp) +set(TARGET_NAME insert_retrieve_dataset_mongodb) +set(SOURCE_FILES insert_retrieve_dataset_mongodb.cpp) ################################ diff --git a/tests/automatic/mongo_db/insert_dataset/cleanup_linux.sh b/tests/automatic/mongo_db/insert_retrieve_dataset/cleanup_linux.sh similarity index 100% rename from tests/automatic/mongo_db/insert_dataset/cleanup_linux.sh rename to tests/automatic/mongo_db/insert_retrieve_dataset/cleanup_linux.sh diff --git a/tests/automatic/mongo_db/insert_dataset/cleanup_windows.bat b/tests/automatic/mongo_db/insert_retrieve_dataset/cleanup_windows.bat similarity index 100% rename from tests/automatic/mongo_db/insert_dataset/cleanup_windows.bat rename to tests/automatic/mongo_db/insert_retrieve_dataset/cleanup_windows.bat diff --git a/tests/automatic/mongo_db/insert_dataset/insert_dataset_mongodb.cpp b/tests/automatic/mongo_db/insert_retrieve_dataset/insert_retrieve_dataset_mongodb.cpp similarity index 80% rename from tests/automatic/mongo_db/insert_dataset/insert_dataset_mongodb.cpp rename to tests/automatic/mongo_db/insert_retrieve_dataset/insert_retrieve_dataset_mongodb.cpp index eea1a1a8469dec4173583e929ba71941a53e81cb..944bfed46380403f52662a9f6da1013c7e6013d4 100644 --- a/tests/automatic/mongo_db/insert_dataset/insert_dataset_mongodb.cpp +++ b/tests/automatic/mongo_db/insert_retrieve_dataset/insert_retrieve_dataset_mongodb.cpp @@ -6,6 +6,8 @@ using asapo::M_AssertContains; +using asapo::M_AssertTrue; + using asapo::Error; @@ -44,7 +46,7 @@ int main(int argc, char* argv[]) { fi.modify_date = std::chrono::system_clock::now(); fi.buf_id = 18446744073709551615ull; fi.source = "host:1234"; - fi.id = 1; + fi.id = 10; uint64_t subset_size = 2; @@ -65,5 +67,15 @@ int main(int argc, char* argv[]) { Assert(err, args.keyword); + if (args.keyword == "OK") { // check retrieve + asapo::FileInfo fi_db; + err = db.GetDataSetById("test", subset_id, fi.id, &fi_db); + M_AssertTrue(fi_db == fi, "get record from db"); + Assert(err, "OK"); + err = db.GetDataSetById("test", 0, 0, &fi_db); + Assert(err, "No record"); + } + + return 0; } diff --git a/tests/automatic/producer/beamtime_metadata/beamtime_metadata.cpp b/tests/automatic/producer/beamtime_metadata/beamtime_metadata.cpp index 044ca7c9374846b23823489e40ffdae9f273ba02..2b72594b76e8e6106a8bb993b1e7f21234f0fecc 100644 --- a/tests/automatic/producer/beamtime_metadata/beamtime_metadata.cpp +++ b/tests/automatic/producer/beamtime_metadata/beamtime_metadata.cpp @@ -69,7 +69,7 @@ std::unique_ptr<asapo::Producer> CreateProducer(const Args& args) { auto producer = asapo::Producer::Create(args.receiver_address, 1, args.mode == 0 ? asapo::RequestHandlerType::kTcp : asapo::RequestHandlerType::kFilesystem, - asapo::SourceCredentials{args.beamtime_id, "", ""}, &err); + asapo::SourceCredentials{args.beamtime_id, "", ""}, 60, &err); if (err) { std::cerr << "Cannot start producer. ProducerError: " << err << std::endl; exit(EXIT_FAILURE); diff --git a/tests/automatic/producer/python_api/check_linux.sh b/tests/automatic/producer/python_api/check_linux.sh index 3a332e03d9866d8b987a11df9647ccedc469289c..6cd5fa90823e713b6ab0461db3aa7981e714dc4c 100644 --- a/tests/automatic/producer/python_api/check_linux.sh +++ b/tests/automatic/producer/python_api/check_linux.sh @@ -41,5 +41,7 @@ sleep 1 $1 $3 $stream $beamtime_id "127.0.0.1:8400" > out || cat out cat out -cat out | grep "successfuly sent" | wc -l | grep 8 -cat out | grep "local i/o error" \ No newline at end of file +cat out | grep "successfuly sent" | wc -l | grep 10 +cat out | grep "local i/o error" +cat out | grep "already have record with same id" | wc -l | grep 4 +cat out | grep "duplicate" | wc -l | grep 4 diff --git a/tests/automatic/producer/python_api/check_windows.bat b/tests/automatic/producer/python_api/check_windows.bat index 01c8380785c2959e04b0ead2a8395cfdcfe81a45..6d8d3ddb2ee2a7ff6d528c3eadec61e418dd66cf 100644 --- a/tests/automatic/producer/python_api/check_windows.bat +++ b/tests/automatic/producer/python_api/check_windows.bat @@ -4,7 +4,7 @@ SET beamline=test SET stream=python SET receiver_root_folder=c:\tmp\asapo\receiver\files SET receiver_folder="%receiver_root_folder%\test_facility\gpfs\%beamline%\2019\data\%beamtime_id%" -SET dbname = %beamtime_id%_%stream% +SET dbname=%beamtime_id%_%stream% echo db.%dbname%.insert({dummy:1})" | %mongo_exe% %dbname% @@ -27,7 +27,14 @@ set PYTHONPATH=%2 type out set NUM=0 for /F %%N in ('find /C "successfuly sent" ^< "out"') do set NUM=%%N -echo %NUM% | findstr 8 || goto error +echo %NUM% | findstr 10 || goto error + +for /F %%N in ('find /C "already have record with same id" ^< "out"') do set NUM=%%N +echo %NUM% | findstr 4 || goto error + + +for /F %%N in ('find /C "duplicate" ^< "out"') do set NUM=%%N +echo %NUM% | findstr 4 || goto error goto :clean diff --git a/tests/automatic/producer/python_api/producer_api.py b/tests/automatic/producer/python_api/producer_api.py index e6905705c96dbbada1c22c259aa2334c53f9dd03..084af55680c72cb65808c84fef9c30f1245a3f11 100644 --- a/tests/automatic/producer/python_api/producer_api.py +++ b/tests/automatic/producer/python_api/producer_api.py @@ -16,21 +16,23 @@ nthreads = 8 def callback(header,err): lock.acquire() # to print - if err is not None: + if isinstance(err,asapo_producer.AsapoServerWarning): + print("successfuly sent, but with warning from server: ",header,err) + elif err is not None: print("could not sent: ",header,err) else: print ("successfuly sent: ",header) lock.release() -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads,60) -producer.set_log_level("info") +producer.set_log_level("debug") #send single file producer.send_file(1, local_path = "./file1", exposed_path = stream+"/"+"file1", user_meta = '{"test_key":"test_val"}', callback = callback) #send single file without callback -producer.send_file(1, local_path = "./file1", exposed_path = stream+"/"+"file1", user_meta = '{"test_key":"test_val"}',callback=None) +producer.send_file(10, local_path = "./file1", exposed_path = stream+"/"+"file10", user_meta = '{"test_key":"test_val"}',callback=None) #send subsets producer.send_file(2, local_path = "./file1", exposed_path = stream+"/"+"file2",subset=(2,2),user_meta = '{"test_key":"test_val"}', callback = callback) @@ -62,8 +64,8 @@ producer.send_data(8, stream+"/"+"file8",x, ingest_mode = asapo_producer.DEFAULT_INGEST_MODE, callback = callback) try: - x = x.T - producer.send_data(8, stream+"/"+"file8",x, + x = x.T + producer.send_data(8, stream+"/"+"file8",x, ingest_mode = asapo_producer.DEFAULT_INGEST_MODE, callback = callback) except asapo_producer.AsapoWrongInputError as e: print(e) @@ -72,16 +74,29 @@ else: sys.exit(1) +#send single file once again +producer.send_file(1, local_path = "./file1", exposed_path = stream+"/"+"file1", user_meta = '{"test_key":"test_val"}', callback = callback) +#send metadata only once again +producer.send_data(6, stream+"/"+"file7",None, + ingest_mode = asapo_producer.INGEST_MODE_TRANSFER_METADATA_ONLY, callback = callback) + +#send same id different data +producer.send_file(1, local_path = "./file1", exposed_path = stream+"/"+"file1", user_meta = '{"test_key1":"test_val"}', callback = callback)#send same id different data +producer.send_data(6, stream+"/"+"file8",None, + ingest_mode = asapo_producer.INGEST_MODE_TRANSFER_METADATA_ONLY, callback = callback) + + + producer.wait_requests_finished(50000) n = producer.get_requests_queue_size() if n!=0: - print("number of remaining requests should be zero, got ",n) - sys.exit(1) + print("number of remaining requests should be zero, got ",n) + sys.exit(1) # create with error try: - producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, 0) + producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, 0,0) except asapo_producer.AsapoWrongInputError as e: print(e) else: @@ -91,4 +106,3 @@ else: - diff --git a/tests/automatic/system_io/ip_tcp_network/client_serv/ip_tcp_network.cpp b/tests/automatic/system_io/ip_tcp_network/client_serv/ip_tcp_network.cpp index 08149f98a2c3f9e83e9092a742aef3532d28f4dd..31517dec86243b196fee64c4c7cdac2cdd38d99d 100644 --- a/tests/automatic/system_io/ip_tcp_network/client_serv/ip_tcp_network.cpp +++ b/tests/automatic/system_io/ip_tcp_network/client_serv/ip_tcp_network.cpp @@ -17,8 +17,7 @@ using asapo::M_AssertEq; using namespace std::chrono; -static const std::unique_ptr<asapo::IO> -io(asapo::GenerateDefaultIO()); +static const std::unique_ptr<asapo::IO> io(asapo::GenerateDefaultIO()); static const std::string kListenAddress = "127.0.0.1:60123"; static std::promise<void> kThreadStarted; static const int kNumberOfChecks = 2; @@ -79,7 +78,7 @@ std::unique_ptr<std::thread> CreateEchoServerThread() { io->Send(client_fd, buffer.get(), received, &err); ExitIfErrIsNotOk(&err, 107); - io->ReceiveDataToFile(client_fd, "", "received", need_to_receive_size, false); + io->ReceiveDataToFile(client_fd, "", "received", need_to_receive_size, false, true); buffer = io->GetDataFromFile("received", &need_to_receive_size, &err); io->Send(client_fd, buffer.get(), received, &err); ExitIfErrIsNotOk(&err, 108); diff --git a/tests/automatic/system_io/write_data_to_file/write_data_to_file.cpp b/tests/automatic/system_io/write_data_to_file/write_data_to_file.cpp index edc3e5179a01076f067fd4149674afea1a77b3f6..8231f5f4b18df122ddfcda8771cc7070bfc815c6 100644 --- a/tests/automatic/system_io/write_data_to_file/write_data_to_file.cpp +++ b/tests/automatic/system_io/write_data_to_file/write_data_to_file.cpp @@ -55,10 +55,14 @@ int main(int argc, char* argv[]) { auto array = new uint8_t[params.length] {'1', '2', '3'}; FileData data{array}; - auto err = io->WriteDataToFile("", params.fname, data, params.length, true); + auto err = io->WriteDataToFile("", params.fname, data, params.length, true, true); if (params.result == "ok") { AssertGoodResult(io, err, data, params); + // check allow_overwrite works + auto err = io->WriteDataToFile("", params.fname, data, params.length, true, false); + params.message = asapo::IOErrorTemplates::kFileAlreadyExists.Generate()->Explain(); + AssertBadResult(err, params); } else { AssertBadResult(err, params); } diff --git a/tests/manual/python_tests/producer/test.py b/tests/manual/python_tests/producer/test.py index 83ea7b4251bf3ab81b6a9e713edaabf8cc834456..b5354b29daf550583db5cd6a3efc6fa4000bdf2e 100644 --- a/tests/manual/python_tests/producer/test.py +++ b/tests/manual/python_tests/producer/test.py @@ -27,7 +27,7 @@ def assert_err(err): print(err) sys.exit(1) -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads ,0) producer.set_log_level("info") diff --git a/tests/manual/python_tests/producer_wait_bug_mongo/test.py b/tests/manual/python_tests/producer_wait_bug_mongo/test.py index 2725862d91e48a82aa11c34a9d6896f44be45d4f..99d063b467a4f2fe9cfc498a94ab1252e333f638 100644 --- a/tests/manual/python_tests/producer_wait_bug_mongo/test.py +++ b/tests/manual/python_tests/producer_wait_bug_mongo/test.py @@ -27,7 +27,7 @@ def assert_err(err): print(err) sys.exit(1) -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads, 600) producer.set_log_level("debug") diff --git a/tests/manual/python_tests/producer_wait_threads/producer_api.py b/tests/manual/python_tests/producer_wait_threads/producer_api.py index d4de005f366ccea59d20bbcd2881c02d88354b80..e15ce89b8b6214fe763b6eb8f66697e9404e0dc9 100644 --- a/tests/manual/python_tests/producer_wait_threads/producer_api.py +++ b/tests/manual/python_tests/producer_wait_threads/producer_api.py @@ -22,7 +22,7 @@ def callback(header,err): print ("successfuly sent: ",header) lock.release() -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads, 600) producer.set_log_level("info") @@ -63,7 +63,7 @@ if n!=0: # create with error try: - producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, 0) + producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, 0, 600) except Exception as Asapo: print(e) else: diff --git a/tests/manual/python_tests/producer_wait_threads/test.py b/tests/manual/python_tests/producer_wait_threads/test.py index 3a0206d73ac7b556e751a3e7a848ea19c44b7513..70c04381ed06d5b10819b35146a6f064b2c26f79 100644 --- a/tests/manual/python_tests/producer_wait_threads/test.py +++ b/tests/manual/python_tests/producer_wait_threads/test.py @@ -22,7 +22,7 @@ def callback(header,err): print ("successfuly sent: ",header) lock.release() -producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads) +producer = asapo_producer.create_producer(endpoint,beamtime, stream, token, nthreads, 600) producer.set_log_level("info")