diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 5871ead42f2b9fef62c1bfa9ea6189642ea59c4f..5074a479657ac0aee77ede05636325d4c77f05d6 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -13,6 +13,7 @@ - cta/CTA#983 Add cta-release package for public binary rpm distribution. - cta/CTA#980 Add external encryption script option - cta/CTA#976 Define a new table in the DB schema to contain the drive status. +- cta/CTA#834 New command "recycletf restore" allows undeleting a copy of a file from tape deleted using tapefile rm - [frontend] New command "tapefile rm" allows deleting a copy of a file from tape diff --git a/catalogue/Catalogue.hpp b/catalogue/Catalogue.hpp index 0a93a611bcf64b3db66f02e3b22617b1e29c0b51..60b813fdc9674eebc07e005939983ea9b71d6b12 100644 --- a/catalogue/Catalogue.hpp +++ b/catalogue/Catalogue.hpp @@ -123,6 +123,7 @@ CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedStorageClassUsedByArchiveFiles); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedStorageClassUsedByArchiveRoutes); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedStorageClassUsedByFileRecycleLogs); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedTapePoolUsedInAnArchiveRoute); +CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedExistingDeletedFileCopy); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedANonExistentTapeState); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedAnEmptyStringReasonWhenTapeStateNotActive); @@ -900,6 +901,15 @@ public: * @return The deleted archive files ordered by archive file ID. */ virtual FileRecycleLogItor getFileRecycleLogItor(const RecycleTapeFileSearchCriteria & searchCriteria = RecycleTapeFileSearchCriteria()) const = 0; + + + /** + * Restores the deleted files in the Recycle log that match the criteria passed + * + * @param searchCriteria The search criteria + */ + virtual void restoreFilesInRecycleLog(const RecycleTapeFileSearchCriteria & searchCriteria = RecycleTapeFileSearchCriteria()) = 0; + /** * Returns the specified files in tape file sequence order. @@ -969,6 +979,7 @@ public: const std::string &reason) = 0; + /** * Returns the archive file with the specified unique identifier. * diff --git a/catalogue/CatalogueRetryWrapper.hpp b/catalogue/CatalogueRetryWrapper.hpp index 84eed51251e79a2a6754591513f9bda6d952c514..efa9e0f2bb02cff8f34bc57ae8edb404ef8e2454 100644 --- a/catalogue/CatalogueRetryWrapper.hpp +++ b/catalogue/CatalogueRetryWrapper.hpp @@ -533,6 +533,10 @@ public: return retryOnLostConnection(m_log, [&]{return m_catalogue->getFileRecycleLogItor(searchCriteria);}, m_maxTriesToConnect); } + void restoreFilesInRecycleLog(const RecycleTapeFileSearchCriteria & searchCriteria) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->restoreFilesInRecycleLog(searchCriteria);}, m_maxTriesToConnect); + } + void deleteFileFromRecycleBin(const uint64_t archiveFileId, log::LogContext &lc){ return retryOnLostConnection(m_log,[&]{return m_catalogue->deleteFileFromRecycleBin(archiveFileId,lc);},m_maxTriesToConnect); } diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp index a931dfdc3eb290888f2e3be92c3be832b920210f..c21f770b83bb79adf848b84be8f5a9edbe8995e4 100644 --- a/catalogue/CatalogueTest.cpp +++ b/catalogue/CatalogueTest.cpp @@ -124,6 +124,17 @@ namespace { return storageClass; } + cta::common::dataStructures::StorageClass getStorageClassTripleCopy() { + using namespace cta; + + common::dataStructures::StorageClass storageClass; + storageClass.name = "storage_class_triple_copy"; + storageClass.nbCopies = 3; + storageClass.vo.name = getVo().name; + storageClass.comment = "Creation of storage class with 3 copies on tape"; + return storageClass; + } + cta::catalogue::MediaType getMediaType() { using namespace cta; @@ -164,6 +175,14 @@ namespace { tape.comment = "Creation of tape two"; return tape; } + + cta::catalogue::CreateTapeAttributes getTape3() { + // Tape 3 is an exact copy of tape 1 except for its VID and comment + auto tape = getTape1(); + tape.vid = "VIDTHREE"; + tape.comment = "Creation of tape three"; + return tape; + } cta::catalogue::CreateMountPolicyAttributes getMountPolicy1() { using namespace cta; @@ -191,9 +210,11 @@ cta_catalogue_CatalogueTest::cta_catalogue_CatalogueTest(): m_storageClassSingleCopy(getStorageClass()), m_anotherStorageClass(getAnotherStorageClass()), m_storageClassDualCopy(getStorageClassDualCopy()), + m_storageClassTripleCopy(getStorageClassTripleCopy()), m_mediaType(getMediaType()), m_tape1(getTape1()), - m_tape2(getTape2()) { + m_tape2(getTape2()), + m_tape3(getTape3()) { } //------------------------------------------------------------------------------ @@ -16097,4 +16118,452 @@ TEST_P(cta_catalogue_CatalogueTest, DeleteTapeFileCopyUsingFXID) { } } +TEST_P(cta_catalogue_CatalogueTest, RestoreTapeFileCopy) { + using namespace cta; + + const bool logicalLibraryIsDisabled= false; + const std::string tapePoolName1 = "tape_pool_name_1"; + const std::string tapePoolName2 = "tape_pool_name_2"; + const uint64_t nbPartialTapes = 1; + const bool isEncrypted = true; + const cta::optional<std::string> supply("value for the supply pool mechanism"); + const std::string diskInstance = "disk_instance"; + const std::string tapeDrive = "tape_drive"; + const std::string reason = "reason"; + + m_catalogue->createMediaType(m_admin, m_mediaType); + m_catalogue->createLogicalLibrary(m_admin, m_tape1.logicalLibraryName, logicalLibraryIsDisabled, "Create logical library"); + m_catalogue->createVirtualOrganization(m_admin, m_vo); + m_catalogue->createTapePool(m_admin, tapePoolName1, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createTapePool(m_admin, tapePoolName2, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createStorageClass(m_admin, m_storageClassDualCopy); + + auto tape1 = m_tape1; + auto tape2 = m_tape2; + tape1.tapePoolName = tapePoolName1; + tape2.tapePoolName = tapePoolName2; + + m_catalogue->createTape(m_admin, tape1); + m_catalogue->createTape(m_admin, tape2); + + ASSERT_FALSE(m_catalogue->getArchiveFilesItor().hasMore()); + const uint64_t archiveFileSize = 2 * 1000 * 1000 * 1000; + + + // Write a file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassDualCopy.name; + fileWritten.vid = tape1.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 1; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + + // Write a second copy of file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassDualCopy.name; + fileWritten.vid = tape2.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 2; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + { + //Assert both copies written + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(2, archiveFile.tapeFiles.size()); + } + + { + //delete copy of file on tape1 + m_catalogue->deleteTapeFileCopy(tape1.vid, "BC614D", diskInstance, reason); + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(1, archiveFile.tapeFiles.size()); + } + + + { + //restore copy of file on tape1 + catalogue::RecycleTapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = 1; + searchCriteria.vid = tape1.vid; + + m_catalogue->restoreFilesInRecycleLog(searchCriteria); + auto archiveFile = m_catalogue->getArchiveFileById(1); + //assert both copies present + ASSERT_EQ(2, archiveFile.tapeFiles.size()); + + //assert recycle log is empty + auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(); + ASSERT_FALSE(fileRecycleLogItor.hasMore()); + + } +} + +TEST_P(cta_catalogue_CatalogueTest, RestoreRewrittenTapeFileCopyFails) { + using namespace cta; + + const bool logicalLibraryIsDisabled= false; + const std::string tapePoolName1 = "tape_pool_name_1"; + const std::string tapePoolName2 = "tape_pool_name_2"; + const uint64_t nbPartialTapes = 1; + const bool isEncrypted = true; + const cta::optional<std::string> supply("value for the supply pool mechanism"); + const std::string diskInstance = "disk_instance"; + const std::string tapeDrive = "tape_drive"; + const std::string reason = "reason"; + + m_catalogue->createMediaType(m_admin, m_mediaType); + m_catalogue->createLogicalLibrary(m_admin, m_tape1.logicalLibraryName, logicalLibraryIsDisabled, "Create logical library"); + m_catalogue->createVirtualOrganization(m_admin, m_vo); + m_catalogue->createTapePool(m_admin, tapePoolName1, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createTapePool(m_admin, tapePoolName2, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createStorageClass(m_admin, m_storageClassDualCopy); + + auto tape1 = m_tape1; + auto tape2 = m_tape2; + tape1.tapePoolName = tapePoolName1; + tape2.tapePoolName = tapePoolName2; + + m_catalogue->createTape(m_admin, tape1); + m_catalogue->createTape(m_admin, tape2); + + ASSERT_FALSE(m_catalogue->getArchiveFilesItor().hasMore()); + const uint64_t archiveFileSize = 2 * 1000 * 1000 * 1000; + + + // Write a file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassDualCopy.name; + fileWritten.vid = tape1.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 1; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + + // Write a second copy of file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassDualCopy.name; + fileWritten.vid = tape2.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 2; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + { + //Assert both copies written + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(2, archiveFile.tapeFiles.size()); + } + + { + //delete copy of file on tape1 + m_catalogue->deleteTapeFileCopy(tape1.vid, "BC614D", diskInstance, reason); + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(1, archiveFile.tapeFiles.size()); + } + + // Rewrite deleted copy of file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassDualCopy.name; + fileWritten.vid = tape1.vid; + fileWritten.fSeq = 2; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 1; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + + { + //restore copy of file on tape1 + catalogue::RecycleTapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = 1; + searchCriteria.vid = tape1.vid; + + ASSERT_THROW(m_catalogue->restoreFilesInRecycleLog(searchCriteria), catalogue::UserSpecifiedExistingDeletedFileCopy); + auto archiveFile = m_catalogue->getArchiveFileById(1); + //assert only two copies present + ASSERT_EQ(2, archiveFile.tapeFiles.size()); + + //assert recycle log still contains deleted copy + auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(); + ASSERT_TRUE(fileRecycleLogItor.hasMore()); + + } +} + +TEST_P(cta_catalogue_CatalogueTest, RestoreVariousDeletedTapeFileCopies) { + using namespace cta; + + const bool logicalLibraryIsDisabled= false; + const std::string tapePoolName1 = "tape_pool_name_1"; + const std::string tapePoolName2 = "tape_pool_name_2"; + const std::string tapePoolName3 = "tape_pool_name_3"; + const uint64_t nbPartialTapes = 1; + const bool isEncrypted = true; + const cta::optional<std::string> supply("value for the supply pool mechanism"); + const std::string diskInstance = "disk_instance"; + const std::string tapeDrive = "tape_drive"; + const std::string reason = "reason"; + + m_catalogue->createMediaType(m_admin, m_mediaType); + m_catalogue->createLogicalLibrary(m_admin, m_tape1.logicalLibraryName, logicalLibraryIsDisabled, "Create logical library"); + m_catalogue->createVirtualOrganization(m_admin, m_vo); + m_catalogue->createTapePool(m_admin, tapePoolName1, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createTapePool(m_admin, tapePoolName2, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createTapePool(m_admin, tapePoolName3, m_vo.name, nbPartialTapes, isEncrypted, supply, "Create tape pool"); + m_catalogue->createStorageClass(m_admin, m_storageClassTripleCopy); + + auto tape1 = m_tape1; + auto tape2 = m_tape2; + auto tape3 = m_tape3; + tape1.tapePoolName = tapePoolName1; + tape2.tapePoolName = tapePoolName2; + tape3.tapePoolName = tapePoolName3; + + m_catalogue->createTape(m_admin, tape1); + m_catalogue->createTape(m_admin, tape2); + m_catalogue->createTape(m_admin, tape3); + + ASSERT_FALSE(m_catalogue->getArchiveFilesItor().hasMore()); + const uint64_t archiveFileSize = 2 * 1000 * 1000 * 1000; + + + // Write a file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassTripleCopy.name; + fileWritten.vid = tape1.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 1; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + + // Write a second copy of file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassTripleCopy.name; + fileWritten.vid = tape2.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 2; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + + // Write a third copy of file on tape + { + std::set<catalogue::TapeItemWrittenPointer> tapeFilesWrittenCopy1; + + std::ostringstream diskFileId; + diskFileId << 12345677; + + std::ostringstream diskFilePath; + diskFilePath << "/test/file1"; + + auto fileWrittenUP=cta::make_unique<cta::catalogue::TapeFileWritten>(); + auto & fileWritten = *fileWrittenUP; + fileWritten.archiveFileId = 1; + fileWritten.diskInstance = diskInstance; + fileWritten.diskFileId = diskFileId.str(); + fileWritten.diskFilePath = diskFilePath.str(); + fileWritten.diskFileOwnerUid = PUBLIC_DISK_USER; + fileWritten.diskFileGid = PUBLIC_DISK_GROUP; + fileWritten.size = archiveFileSize; + fileWritten.checksumBlob.insert(checksum::ADLER32, "1357"); + fileWritten.storageClassName = m_storageClassTripleCopy.name; + fileWritten.vid = tape3.vid; + fileWritten.fSeq = 1; + fileWritten.blockId = 1 * 100; + fileWritten.copyNb = 3; + fileWritten.tapeDrive = tapeDrive; + tapeFilesWrittenCopy1.emplace(fileWrittenUP.release()); + + m_catalogue->filesWrittenToTape(tapeFilesWrittenCopy1); + } + { + //Assert all copies written + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(3, archiveFile.tapeFiles.size()); + } + + { + //delete copy of file on tape1 + m_catalogue->deleteTapeFileCopy(tape1.vid, "BC614D", diskInstance, reason); + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(2, archiveFile.tapeFiles.size()); + } + + { + //delete copy of file on tape2 + m_catalogue->deleteTapeFileCopy(tape2.vid, "BC614D", diskInstance, reason); + auto archiveFile = m_catalogue->getArchiveFileById(1); + ASSERT_EQ(1, archiveFile.tapeFiles.size()); + } + + + { + //restore all deleted copies + catalogue::RecycleTapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = 1; + + m_catalogue->restoreFilesInRecycleLog(searchCriteria); + auto archiveFile = m_catalogue->getArchiveFileById(1); + //assert only two copies present + ASSERT_EQ(3, archiveFile.tapeFiles.size()); + + //assert recycle log still contains deleted copy + auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(); + ASSERT_FALSE(fileRecycleLogItor.hasMore()); + + } +} + + } // namespace unitTests diff --git a/catalogue/CatalogueTest.hpp b/catalogue/CatalogueTest.hpp index b15bb24f6b9e6a3f3c22bec44c2997921388acfa..d556ad63aa0688e09d1bec7bff67fb21d4fc6ab2 100644 --- a/catalogue/CatalogueTest.hpp +++ b/catalogue/CatalogueTest.hpp @@ -46,9 +46,11 @@ protected: const cta::common::dataStructures::StorageClass m_storageClassSingleCopy; const cta::common::dataStructures::StorageClass m_anotherStorageClass; const cta::common::dataStructures::StorageClass m_storageClassDualCopy; + const cta::common::dataStructures::StorageClass m_storageClassTripleCopy; const cta::catalogue::MediaType m_mediaType; const cta::catalogue::CreateTapeAttributes m_tape1; const cta::catalogue::CreateTapeAttributes m_tape2; + const cta::catalogue::CreateTapeAttributes m_tape3; virtual void SetUp(); diff --git a/catalogue/DummyCatalogue.hpp b/catalogue/DummyCatalogue.hpp index 62e5deed52b9b3e050f676c6b394fb674ac15c8b..a0082a00c05f3eca5d9fbb4cd4cf7fb43db53865 100644 --- a/catalogue/DummyCatalogue.hpp +++ b/catalogue/DummyCatalogue.hpp @@ -82,6 +82,7 @@ public: common::dataStructures::ArchiveFile getArchiveFileById(const uint64_t id) const override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } ArchiveFileItor getArchiveFilesItor(const TapeFileSearchCriteria& searchCriteria) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } FileRecycleLogItor getFileRecycleLogItor(const RecycleTapeFileSearchCriteria & searchCriteria) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + void restoreFilesInRecycleLog(const RecycleTapeFileSearchCriteria & searchCriteria) { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void deleteFileFromRecycleBin(const uint64_t archiveFileId, log::LogContext &lc) {throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} void deleteFilesFromRecycleLog(const std::string & vid, log::LogContext & lc) {throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} std::list<common::dataStructures::ArchiveFile> getFilesForRepack(const std::string &vid, const uint64_t startFSeq, const uint64_t maxNbFiles) const override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } diff --git a/catalogue/MysqlCatalogue.cpp b/catalogue/MysqlCatalogue.cpp index f9c184f415ac7979b9c60bae07289dbd074117c3..f8f450b0064fdf6a635e4bc723e48e32fd8c6b1d 100644 --- a/catalogue/MysqlCatalogue.cpp +++ b/catalogue/MysqlCatalogue.cpp @@ -820,5 +820,95 @@ void MysqlCatalogue::copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, co } } +//------------------------------------------------------------------------------ +// restoreFileCopiesInRecycleLog +//------------------------------------------------------------------------------ +void MysqlCatalogue::restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) { +try { + utils::Timer t; + log::TimingList tl; + + //put fileRecycleLogs in std::list so we can release the underlying fileRecycleLogItor database connection + //otherwise we are using two conns when calling getArchiveFilesItor + std::list<common::dataStructures::FileRecycleLog> fileRecycleLogList; + while (fileRecycleLogItor.hasMore()) { + auto fileRecycleLog = fileRecycleLogItor.next(); + fileRecycleLogList.push_back(fileRecycleLog); + } + + //We currently do all file copies restoring in a single transaction + conn.executeNonQuery("START TRANSACTION"); + for (auto &fileRecycleLog: fileRecycleLogList) { + TapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = fileRecycleLog.archiveFileId; + searchCriteria.diskInstance = fileRecycleLog.diskInstanceName; + searchCriteria.diskFileIds = std::vector<std::string>(); + searchCriteria.diskFileIds.value().push_back(fileRecycleLog.diskFileId); + + auto itor = getArchiveFilesItor(conn, searchCriteria); + if (itor.hasMore()) { + //only restore file copies, do nothing if file has been completely deleted in CTA + cta::common::dataStructures::ArchiveFile archiveFile = itor.next(); + if (archiveFile.tapeFiles.find(fileRecycleLog.copyNb) != archiveFile.tapeFiles.end()) { + //copy with same copy_nb exists, cannot restore + UserSpecifiedExistingDeletedFileCopy ex; + ex.getMessage() << "Cannot restore file copy with archiveFileId " << std::to_string(fileRecycleLog.archiveFileId) + << " and copy_nb " << std::to_string(fileRecycleLog.copyNb) << " because a tapefile with same archiveFileId and copy_nb already exists"; + throw ex; + } + restoreFileCopyInRecycleLog(conn, fileRecycleLog, lc); + } + } + conn.commit(); + + log::ScopedParamContainer spc(lc); + tl.insertAndReset("commitTime",t); + tl.addToLog(spc); + lc.log(log::INFO,"In MysqlCatalogue::restoreFileCopiesInRecycleLog: all file copies successfully restored."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// restoreFileCopyInRecycleLog +//------------------------------------------------------------------------------ +void MysqlCatalogue::restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLog, log::LogContext & lc) { + try { + utils::Timer t; + log::TimingList tl; + cta::common::dataStructures::TapeFile tapeFile; + tapeFile.vid = fileRecycleLog.vid; + tapeFile.fSeq = fileRecycleLog.fSeq; + tapeFile.copyNb = fileRecycleLog.copyNb; + tapeFile.blockId = fileRecycleLog.blockId; + tapeFile.fileSize = fileRecycleLog.sizeInBytes; + tapeFile.creationTime = fileRecycleLog.tapeFileCreationTime; + + insertTapeFile(conn, tapeFile, fileRecycleLog.archiveFileId); + tl.insertAndReset("insertTapeFileTime",t); + + deleteTapeFileCopyFromRecycleBin(conn, fileRecycleLog); + tl.insertAndReset("deleteTapeFileCopyFromRecycleBinTime",t); + + log::ScopedParamContainer spc(lc); + spc.add("vid", tapeFile.vid); + spc.add("archiveFileId", fileRecycleLog.archiveFileId); + spc.add("fSeq", tapeFile.fSeq); + spc.add("copyNb", tapeFile.copyNb); + spc.add("fileSize", tapeFile.fileSize); + tl.addToLog(spc); + lc.log(log::INFO,"In MysqlCatalogue::restoreFileCopyInRecycleLog: File restored from the recycle log."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + } // namespace catalogue } // namespace cta diff --git a/catalogue/MysqlCatalogue.hpp b/catalogue/MysqlCatalogue.hpp index 74ed0b0f5efd921bb7e425d520b7ea0f8af0ab55..75df118fd39fbd9d4da9adf8d5f62617771f7a5b 100644 --- a/catalogue/MysqlCatalogue.hpp +++ b/catalogue/MysqlCatalogue.hpp @@ -219,6 +219,22 @@ protected: void copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, const cta::common::dataStructures::ArchiveFile &file, const std::string &reason, log::LogContext & lc) override; + /** + * Copy the files in fileRecycleLogItor to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entries + * @param conn the database connection + * @param fileRecycleLogItor the collection of fileRecycleLogs we want to restore + * @param lc the log context + */ + void restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) override; + + /** + * Copy the fileRecycleLog to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entry + * @param conn the database connection + * @param fileRecycleLog the fileRecycleLog we want to restore + * @param lc the log context + */ + void restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLogItor, log::LogContext & lc); + private: diff --git a/catalogue/OracleCatalogue.cpp b/catalogue/OracleCatalogue.cpp index e15b23d3d0f994cb26bc57a0c5f3364ddd859e56..98717eee30bc6ff9500e5670629ec3c5d6cd9c52 100644 --- a/catalogue/OracleCatalogue.cpp +++ b/catalogue/OracleCatalogue.cpp @@ -1149,6 +1149,99 @@ void OracleCatalogue::copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, c } } +//------------------------------------------------------------------------------ +// restoreFileCopiesInRecycleLog +//------------------------------------------------------------------------------ +void OracleCatalogue::restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) { +try { + utils::Timer t; + log::TimingList tl; + + //put fileRecycleLogs in std::list so we can release the underlying fileRecycleLogItor database connection + //otherwise we are using two conns when calling getArchiveFilesItor + std::list<common::dataStructures::FileRecycleLog> fileRecycleLogList; + while (fileRecycleLogItor.hasMore()) { + auto fileRecycleLog = fileRecycleLogItor.next(); + fileRecycleLogList.push_back(fileRecycleLog); + } + + //We currently do all file copies restoring in a single transaction + conn.setAutocommitMode(rdbms::AutocommitMode::AUTOCOMMIT_OFF); + for (auto &fileRecycleLog: fileRecycleLogList) { + TapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = fileRecycleLog.archiveFileId; + searchCriteria.diskInstance = fileRecycleLog.diskInstanceName; + searchCriteria.diskFileIds = std::vector<std::string>(); + searchCriteria.diskFileIds.value().push_back(fileRecycleLog.diskFileId); + + auto itor = getArchiveFilesItor(conn, searchCriteria); + if (itor.hasMore()) { + //only restore file copies, do nothing if file has been completely deleted in CTA + cta::common::dataStructures::ArchiveFile archiveFile = itor.next(); + if (archiveFile.tapeFiles.find(fileRecycleLog.copyNb) != archiveFile.tapeFiles.end()) { + //copy with same copy_nb exists, cannot restore + UserSpecifiedExistingDeletedFileCopy ex; + ex.getMessage() << "Cannot restore file copy with archiveFileId " << std::to_string(fileRecycleLog.archiveFileId) + << " and copy_nb " << std::to_string(fileRecycleLog.copyNb) << " because a tapefile with same archiveFileId and copy_nb already exists"; + throw ex; + } + restoreFileCopyInRecycleLog(conn, fileRecycleLog, lc); + } + } + conn.setAutocommitMode(rdbms::AutocommitMode::AUTOCOMMIT_ON); + conn.commit(); + + log::ScopedParamContainer spc(lc); + tl.insertAndReset("commitTime",t); + tl.addToLog(spc); + lc.log(log::INFO,"In OracleCatalogue::restoreFileCopiesInRecycleLog: all file copies successfully restored."); + + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// restoreFileCopyInRecycleLog +//------------------------------------------------------------------------------ +void OracleCatalogue::restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLog, log::LogContext & lc) { + try { + utils::Timer t; + log::TimingList tl; + cta::common::dataStructures::TapeFile tapeFile; + tapeFile.vid = fileRecycleLog.vid; + tapeFile.fSeq = fileRecycleLog.fSeq; + tapeFile.copyNb = fileRecycleLog.copyNb; + tapeFile.blockId = fileRecycleLog.blockId; + tapeFile.fileSize = fileRecycleLog.sizeInBytes; + tapeFile.creationTime = fileRecycleLog.tapeFileCreationTime; + + insertTapeFile(conn, tapeFile, fileRecycleLog.archiveFileId); + tl.insertAndReset("insertTapeFileTime",t); + + deleteTapeFileCopyFromRecycleBin(conn, fileRecycleLog); + tl.insertAndReset("deleteTapeFileCopyFromRecycleBinTime",t); + + log::ScopedParamContainer spc(lc); + spc.add("vid", tapeFile.vid); + spc.add("archiveFileId", fileRecycleLog.archiveFileId); + spc.add("fSeq", tapeFile.fSeq); + spc.add("copyNb", tapeFile.copyNb); + spc.add("fileSize", tapeFile.fileSize); + tl.addToLog(spc); + lc.log(log::INFO,"In OracleCatalogue::restoreFileCopyInRecycleLog: File restored from the recycle log."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + + } // namespace catalogue } // namespace cta diff --git a/catalogue/OracleCatalogue.hpp b/catalogue/OracleCatalogue.hpp index f0c05cf0176a027691eb1eb17f90958f59a36719..172cb55e0fe1f8502355ec607ce271689a236162 100644 --- a/catalogue/OracleCatalogue.hpp +++ b/catalogue/OracleCatalogue.hpp @@ -262,6 +262,22 @@ private: void copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, const cta::common::dataStructures::ArchiveFile &file, const std::string &reason, log::LogContext & lc) override; + /** + * Copy the files in fileRecycleLogItor to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entries + * @param conn the database connection + * @param fileRecycleLogItor the collection of fileRecycleLogs we want to restore + * @param lc the log context + */ + void restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) override; + + /** + * Copy the fileRecycleLog to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entry + * @param conn the database connection + * @param fileRecycleLog the fileRecycleLog we want to restore + * @param lc the log context + */ + void restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLogItor, log::LogContext & lc); + /** * The size and checksum of a file. */ diff --git a/catalogue/PostgresCatalogue.cpp b/catalogue/PostgresCatalogue.cpp index 8967dd6901303673dbb3df209f735744e46e8534..7b06ba81b33c23e5aa601ed3575d3dc467888885 100644 --- a/catalogue/PostgresCatalogue.cpp +++ b/catalogue/PostgresCatalogue.cpp @@ -1109,5 +1109,96 @@ void PostgresCatalogue::copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, } } +//------------------------------------------------------------------------------ +// restoreFileCopiesInRecycleLog +//------------------------------------------------------------------------------ +void PostgresCatalogue::restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) { +try { + utils::Timer t; + log::TimingList tl; + + //put fileRecycleLogs in std::list so we can release the underlying fileRecycleLogItor database connection + //otherwise we are using two conns when calling getArchiveFilesItor + std::list<common::dataStructures::FileRecycleLog> fileRecycleLogList; + while (fileRecycleLogItor.hasMore()) { + auto fileRecycleLog = fileRecycleLogItor.next(); + fileRecycleLogList.push_back(fileRecycleLog); + } + + //We currently do all file copies restoring in a single transaction + conn.executeNonQuery("BEGIN"); + for (auto &fileRecycleLog: fileRecycleLogList) { + TapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = fileRecycleLog.archiveFileId; + searchCriteria.diskInstance = fileRecycleLog.diskInstanceName; + searchCriteria.diskFileIds = std::vector<std::string>(); + searchCriteria.diskFileIds.value().push_back(fileRecycleLog.diskFileId); + + auto itor = getArchiveFilesItor(conn, searchCriteria); + if (itor.hasMore()) { + //only restore file copies, do nothing if file has been completely deleted in CTA + cta::common::dataStructures::ArchiveFile archiveFile = itor.next(); + if (archiveFile.tapeFiles.find(fileRecycleLog.copyNb) != archiveFile.tapeFiles.end()) { + //copy with same copy_nb exists, cannot restore + UserSpecifiedExistingDeletedFileCopy ex; + ex.getMessage() << "Cannot restore file copy with archiveFileId " << std::to_string(fileRecycleLog.archiveFileId) + << " and copy_nb " << std::to_string(fileRecycleLog.copyNb) << " because a tapefile with same archiveFileId and copy_nb already exists"; + throw ex; + } + restoreFileCopyInRecycleLog(conn, fileRecycleLog, lc); + } + } + conn.commit(); + + log::ScopedParamContainer spc(lc); + tl.insertAndReset("commitTime",t); + tl.addToLog(spc); + lc.log(log::INFO,"In PostgresCatalogue::restoreFileCopiesInRecycleLog: all file copies successfully restored."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// restoreFileCopyInRecycleLog +//------------------------------------------------------------------------------ +void PostgresCatalogue::restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLog, log::LogContext & lc) { + try { + utils::Timer t; + log::TimingList tl; + cta::common::dataStructures::TapeFile tapeFile; + tapeFile.vid = fileRecycleLog.vid; + tapeFile.fSeq = fileRecycleLog.fSeq; + tapeFile.copyNb = fileRecycleLog.copyNb; + tapeFile.blockId = fileRecycleLog.blockId; + tapeFile.fileSize = fileRecycleLog.sizeInBytes; + tapeFile.creationTime = fileRecycleLog.tapeFileCreationTime; + + insertTapeFile(conn, tapeFile, fileRecycleLog.archiveFileId); + tl.insertAndReset("insertTapeFileTime",t); + + deleteTapeFileCopyFromRecycleBin(conn, fileRecycleLog); + tl.insertAndReset("deleteTapeFileCopyFromRecycleBinTime",t); + + log::ScopedParamContainer spc(lc); + spc.add("vid", tapeFile.vid); + spc.add("archiveFileId", fileRecycleLog.archiveFileId); + spc.add("fSeq", tapeFile.fSeq); + spc.add("copyNb", tapeFile.copyNb); + spc.add("fileSize", tapeFile.fileSize); + tl.addToLog(spc); + lc.log(log::INFO,"In PostgresCatalogue::restoreFileCopyInRecycleLog: File restored from the recycle log."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + + } // namespace catalogue } // namespace cta diff --git a/catalogue/PostgresCatalogue.hpp b/catalogue/PostgresCatalogue.hpp index 90bacb1e28ad5833af7c823b934e3423ed2a1cfe..4d50cef0d35d6fcc8062de55f08719d7b0c11ed2 100644 --- a/catalogue/PostgresCatalogue.hpp +++ b/catalogue/PostgresCatalogue.hpp @@ -296,6 +296,22 @@ private: void copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, const cta::common::dataStructures::ArchiveFile &file, const std::string &reason, log::LogContext & lc) override; + /** + * Copy the files in fileRecycleLogItor to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entries + * @param conn the database connection + * @param fileRecycleLogItor the collection of fileRecycleLogs we want to restore + * @param lc the log context + */ + void restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) override; + + /** + * Copy the fileRecycleLog to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entry + * @param conn the database connection + * @param fileRecycleLog the fileRecycleLog we want to restore + * @param lc the log context + */ + void restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLogItor, log::LogContext & lc); + }; // class PostgresCatalogue } // namespace catalogue diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp index a6cd1623c2590cdc665195092c236f34e68fa3f1..0b5e51eb4b69baff50e5d121868bca1b858e315b 100644 --- a/catalogue/RdbmsCatalogue.cpp +++ b/catalogue/RdbmsCatalogue.cpp @@ -6936,7 +6936,14 @@ void RdbmsCatalogue::insertArchiveFile(rdbms::Conn &conn, const ArchiveFileRowWi //------------------------------------------------------------------------------ void RdbmsCatalogue::checkTapeFileSearchCriteria(const TapeFileSearchCriteria &searchCriteria) const { auto conn = m_connPool.getConn(); + checkTapeFileSearchCriteria(conn, searchCriteria); +} + +//------------------------------------------------------------------------------ +// checkTapeFileSearchCriteria +//------------------------------------------------------------------------------ +void RdbmsCatalogue::checkTapeFileSearchCriteria(rdbms::Conn &conn, const TapeFileSearchCriteria &searchCriteria) const { if(searchCriteria.archiveFileId) { if(!archiveFileIdExists(conn, searchCriteria.archiveFileId.value())) { throw exception::UserError(std::string("Archive file with ID ") + @@ -6983,6 +6990,33 @@ Catalogue::ArchiveFileItor RdbmsCatalogue::getArchiveFilesItor(const TapeFileSea } } +//------------------------------------------------------------------------------ +// getArchiveFilesItor +//------------------------------------------------------------------------------ +Catalogue::ArchiveFileItor RdbmsCatalogue::getArchiveFilesItor(rdbms::Conn &conn, const TapeFileSearchCriteria &searchCriteria) const { + + checkTapeFileSearchCriteria(conn, searchCriteria); + + // If this is the listing of the contents of a tape + if (!searchCriteria.archiveFileId && !searchCriteria.diskInstance && !searchCriteria.diskFileIds && + searchCriteria.vid) { + return getTapeContentsItor(searchCriteria.vid.value()); + } + + try { + auto archiveListingConn = m_archiveFileListingConnPool.getConn(); + const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(archiveListingConn, searchCriteria.diskFileIds); + // Pass ownership of the connection to the Iterator object + auto impl = new RdbmsCatalogueGetArchiveFilesItor(m_log, std::move(archiveListingConn), searchCriteria, tempDiskFxidsTableName); + return ArchiveFileItor(impl); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // getTapeContentsItor //------------------------------------------------------------------------------ @@ -7026,6 +7060,24 @@ Catalogue::FileRecycleLogItor RdbmsCatalogue::getFileRecycleLogItor(const Recycl } } + +//------------------------------------------------------------------------------ +// restoreFilesInRecycleLog +//------------------------------------------------------------------------------ +void RdbmsCatalogue::restoreFilesInRecycleLog(const RecycleTapeFileSearchCriteria & searchCriteria) { + try { + auto fileRecycleLogitor = getFileRecycleLogItor(searchCriteria); + auto conn = m_connPool.getConn(); + log::LogContext lc(m_log); + restoreFileCopiesInRecycleLog(conn, fileRecycleLogitor, lc); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // getFilesForRepack //------------------------------------------------------------------------------ @@ -9117,6 +9169,34 @@ void RdbmsCatalogue::deleteArchiveFileFromRecycleBin(rdbms::Conn& conn, const ui } } +//------------------------------------------------------------------------------ +// deleteTapeFileCopyFromRecycleBin +//------------------------------------------------------------------------------ +void RdbmsCatalogue::deleteTapeFileCopyFromRecycleBin(cta::rdbms::Conn & conn, const common::dataStructures::FileRecycleLog fileRecycleLog) { + try { + const char *const deleteTapeFilesSql = + "DELETE FROM " + "FILE_RECYCLE_LOG " + "WHERE FILE_RECYCLE_LOG.ARCHIVE_FILE_ID = :ARCHIVE_FILE_ID AND FILE_RECYCLE_LOG.VID = :VID AND " + "FILE_RECYCLE_LOG.FSEQ = :FSEQ AND FILE_RECYCLE_LOG.COPY_NB = :COPY_NB AND " + "FILE_RECYCLE_LOG.DISK_INSTANCE_NAME = :DISK_INSTANCE_NAME"; + + auto deleteTapeFilesStmt = conn.createStmt(deleteTapeFilesSql); + deleteTapeFilesStmt.bindUint64(":ARCHIVE_FILE_ID", fileRecycleLog.archiveFileId); + deleteTapeFilesStmt.bindString(":VID", fileRecycleLog.vid); + deleteTapeFilesStmt.bindUint64(":FSEQ", fileRecycleLog.fSeq); + deleteTapeFilesStmt.bindUint64(":COPY_NB", fileRecycleLog.copyNb); + deleteTapeFilesStmt.bindString(":DISK_INSTANCE_NAME", fileRecycleLog.diskInstanceName); + deleteTapeFilesStmt.executeNonQuery(); + + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // insertOldCopiesOfFilesIfAnyOnFileRecycleLog //------------------------------------------------------------------------------ diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp index ea0044968364533c48318455d712cb1ee27b10fa..2a72b58c91fa0865017f1e12424a7e6a7eaa0428 100644 --- a/catalogue/RdbmsCatalogue.hpp +++ b/catalogue/RdbmsCatalogue.hpp @@ -839,6 +839,15 @@ public: */ void checkTapeFileSearchCriteria(const TapeFileSearchCriteria &searchCriteria) const; + /** + * Throws a UserError exception if the specified searchCriteria is not valid + * due to a user error. + * + * @param conn The database connection. + * @param searchCriteria The search criteria. + */ + void checkTapeFileSearchCriteria(rdbms::Conn &conn, const TapeFileSearchCriteria &searchCriteria) const; + /** * Returns the specified archive files. Please note that the list of files * is ordered by archive file ID. @@ -848,6 +857,17 @@ public: */ ArchiveFileItor getArchiveFilesItor(const TapeFileSearchCriteria &searchCriteria) const override; + + /** + * Returns the specified archive files. Please note that the list of files + * is ordered by archive file ID. + * + * @param conn The database connection. + * @param searchCriteria The search criteria. + * @return The archive files. + */ + ArchiveFileItor getArchiveFilesItor(rdbms::Conn &conn, const TapeFileSearchCriteria &searchCriteria) const; + /** * Throws a UserError exception if the specified searchCriteria is not valid * due to a user error. @@ -864,6 +884,13 @@ public: */ FileRecycleLogItor getFileRecycleLogItor(const RecycleTapeFileSearchCriteria & searchCriteria) const override; + /** + * Restores the deleted files in the Recycle log that match the criteria passed + * + * @param searchCriteria The search criteria + */ + void restoreFilesInRecycleLog(const RecycleTapeFileSearchCriteria & searchCriteria) override; + /** * Returns the specified files in tape file sequence order. * @@ -1923,6 +1950,14 @@ protected: */ virtual void copyArchiveFileToFileRecyleLogAndDelete(rdbms::Conn & conn,const common::dataStructures::DeleteArchiveRequest &request, log::LogContext & lc) = 0; + /** + * Copy the fileRecycleLog to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entry + * @param conn the database connection + * @param fileRecycleLog the fileRecycleLog we want to restore + * @param lc the log context + */ + virtual void restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) = 0; + /** * Copies the ARCHIVE_FILE and TAPE_FILE entries to the recycle-bin tables * @param conn the database connection @@ -2038,6 +2073,8 @@ protected: */ void deleteTapeFilesFromRecycleBin(rdbms::Conn & conn, const uint64_t archiveFileId); + void deleteTapeFileCopyFromRecycleBin(cta::rdbms::Conn & conn, const common::dataStructures::FileRecycleLog fileRecycleLog); + /** * Delete the archive file from the ARCHIVE_FILE recycle-bin * @param conn the database connection diff --git a/catalogue/RdbmsCatalogueGetArchiveFilesItor.cpp b/catalogue/RdbmsCatalogueGetArchiveFilesItor.cpp index bef739104e2ebef88b109dd4426c7a32ad8a1acd..81c9ac8544cbd56c7698c1c9f649a5a8e8171e78 100644 --- a/catalogue/RdbmsCatalogueGetArchiveFilesItor.cpp +++ b/catalogue/RdbmsCatalogueGetArchiveFilesItor.cpp @@ -142,9 +142,13 @@ RdbmsCatalogueGetArchiveFilesItor::RdbmsCatalogueGetArchiveFilesItor( addedAWhereConstraint = true; } - // Order by FSEQ if we are listing the contents of a tape, else order by archive file ID + // Order by FSEQ if we are listing the contents of a tape, + // by DISK_FILE_ID if listing the contents of a DISK_INSTANCE + // else order by archive file ID if(searchCriteria.vid) { sql += " ORDER BY FSEQ"; + } else if (searchCriteria.diskInstance) { + sql += " ORDER BY DISK_FILE_ID"; } else { sql += " ORDER BY ARCHIVE_FILE_ID, COPY_NB"; } diff --git a/catalogue/SqliteCatalogue.cpp b/catalogue/SqliteCatalogue.cpp index f235c0279b3e224e75d9b0ea14512e7373091b3b..a3f3b98c7a30adc3823ca61e359d5b6b3729213d 100644 --- a/catalogue/SqliteCatalogue.cpp +++ b/catalogue/SqliteCatalogue.cpp @@ -685,5 +685,95 @@ void SqliteCatalogue::copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, c } } +//------------------------------------------------------------------------------ +// restoreFileCopiesInRecycleLog +//------------------------------------------------------------------------------ +void SqliteCatalogue::restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) { +try { + utils::Timer t; + log::TimingList tl; + + //put fileRecycleLogs in std::list so we can release the underlying fileRecycleLogItor database connection + //otherwise we are using two conns when calling getArchiveFilesItor + std::list<common::dataStructures::FileRecycleLog> fileRecycleLogList; + while (fileRecycleLogItor.hasMore()) { + auto fileRecycleLog = fileRecycleLogItor.next(); + fileRecycleLogList.push_back(fileRecycleLog); + } + + //We currently do all file copy restoring in a single transaction + conn.executeNonQuery("BEGIN TRANSACTION"); + for (auto &fileRecycleLog: fileRecycleLogList) { + TapeFileSearchCriteria searchCriteria; + searchCriteria.archiveFileId = fileRecycleLog.archiveFileId; + searchCriteria.diskInstance = fileRecycleLog.diskInstanceName; + searchCriteria.diskFileIds = std::vector<std::string>(); + searchCriteria.diskFileIds.value().push_back(fileRecycleLog.diskFileId); + + auto itor = getArchiveFilesItor(conn, searchCriteria); + if (itor.hasMore()) { + //only restore file copies, do nothing if file has been completely deleted in CTA + cta::common::dataStructures::ArchiveFile archiveFile = itor.next(); + if (archiveFile.tapeFiles.find(fileRecycleLog.copyNb) != archiveFile.tapeFiles.end()) { + //copy with same copy_nb exists, cannot restore + UserSpecifiedExistingDeletedFileCopy ex; + ex.getMessage() << "Cannot restore file copy with archiveFileId " << std::to_string(fileRecycleLog.archiveFileId) + << " and copy_nb " << std::to_string(fileRecycleLog.copyNb) << " because a tapefile with same archiveFileId and copy_nb already exists"; + throw ex; + } + restoreFileCopyInRecycleLog(conn, fileRecycleLog, lc); + } + } + conn.commit(); + + log::ScopedParamContainer spc(lc); + tl.insertAndReset("commitTime",t); + tl.addToLog(spc); + lc.log(log::INFO,"In SqliteCatalogue::restoreFileCopiesInRecycleLog: all file copies successfully restored."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// restoreFileCopyInRecycleLog +//------------------------------------------------------------------------------ +void SqliteCatalogue::restoreFileCopyInRecycleLog(rdbms::Conn &conn, const common::dataStructures::FileRecycleLog &fileRecycleLog, log::LogContext & lc) { + try { + utils::Timer t; + log::TimingList tl; + cta::common::dataStructures::TapeFile tapeFile; + tapeFile.vid = fileRecycleLog.vid; + tapeFile.fSeq = fileRecycleLog.fSeq; + tapeFile.copyNb = fileRecycleLog.copyNb; + tapeFile.blockId = fileRecycleLog.blockId; + tapeFile.fileSize = fileRecycleLog.sizeInBytes; + tapeFile.creationTime = fileRecycleLog.tapeFileCreationTime; + + insertTapeFile(conn, tapeFile, fileRecycleLog.archiveFileId); + tl.insertAndReset("insertTapeFileTime",t); + + deleteTapeFileCopyFromRecycleBin(conn, fileRecycleLog); + tl.insertAndReset("deleteTapeFileCopyFromRecycleBinTime",t); + + log::ScopedParamContainer spc(lc); + spc.add("vid", tapeFile.vid); + spc.add("archiveFileId", fileRecycleLog.archiveFileId); + spc.add("fSeq", tapeFile.fSeq); + spc.add("copyNb", tapeFile.copyNb); + spc.add("fileSize", tapeFile.fileSize); + tl.addToLog(spc); + lc.log(log::INFO,"In SqliteCatalogue::restoreFileCopyInRecycleLog: File restored from the recycle log."); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + } // namespace catalogue } // namespace cta diff --git a/catalogue/SqliteCatalogue.hpp b/catalogue/SqliteCatalogue.hpp index 4102a8532ba2b638de1643b88859246f99aed3aa..70394195df73617de384388c2f98d67583983fb0 100644 --- a/catalogue/SqliteCatalogue.hpp +++ b/catalogue/SqliteCatalogue.hpp @@ -227,6 +227,24 @@ protected: void copyTapeFileToFileRecyleLogAndDelete(rdbms::Conn & conn, const cta::common::dataStructures::ArchiveFile &file, const std::string &reason, log::LogContext & lc) override; + /** + * Copy the files in fileRecycleLogItor to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entries + * @param conn the database connection + * @param fileRecycleLogItor the collection of fileRecycleLogs we want to restore + * @param lc the log context + */ + void restoreFileCopiesInRecycleLog(rdbms::Conn & conn, FileRecycleLogItor &fileRecycleLogItor, log::LogContext & lc) override; + + /** + * Copy the fileRecycleLog to the TAPE_FILE table and deletes the corresponding FILE_RECYCLE_LOG table entry + * @param conn the database connection + * @param fileRecycleLog the fileRecycleLog we want to restore + * @param lc the log context + */ + void restoreFileCopyInRecycleLog(rdbms::Conn & conn, const common::dataStructures::FileRecycleLog &fileRecycleLogItor, log::LogContext & lc); + + + private: /** diff --git a/cmdline/CtaAdminCmd.cpp b/cmdline/CtaAdminCmd.cpp index 334e55dc4ddb09edd295d03c4ed47977f66d947e..eb600d7df98b7d5d967ea15a43f604d2f37e0d1b 100644 --- a/cmdline/CtaAdminCmd.cpp +++ b/cmdline/CtaAdminCmd.cpp @@ -34,6 +34,8 @@ std::atomic<bool> isHeaderSent(false); cta::admin::TextFormatter formattedText(1000); +std::string tp_config_file = "/etc/cta/TPCONFIG"; + namespace XrdSsiPb { /*! @@ -229,7 +231,7 @@ void CtaAdminCmd::send() const } catch(std::runtime_error &ex) { throwUsage(ex.what()); } - + // Set configuration options const std::string config_file = "/etc/cta/cta-cli.conf"; XrdSsiPb::Config config(config_file, "cta"); @@ -346,7 +348,36 @@ void CtaAdminCmd::parseOptions(int start, int argc, const char *const *const arg } } +std::string CtaAdminCmd::getDriveFromTpConfig() { + std::ifstream file(tp_config_file); + if (file.fail()) { + throw std::runtime_error("Unable to open file " + tp_config_file); + } + + std::string line; + while(std::getline(file, line)) { + // Strip out comments + auto pos = line.find('#'); + if(pos != std::string::npos) { + line.resize(pos); + } + + // Extract the list items + std::stringstream ss(line); + while(!ss.eof()) { + std::string item; + ss >> item; + // skip blank lines or lines consisting only of whitespace + if(item.empty()) continue; + + std::string drivename = item.substr(0, item.find(" ")); // first word of line + return drivename; + + } + } + throw std::runtime_error("File " + tp_config_file + " is empty"); +} void CtaAdminCmd::addOption(const Option &option, const std::string &value) { @@ -359,7 +390,11 @@ void CtaAdminCmd::addOption(const Option &option, const std::string &value) auto key = strOptions.at(option.get_key()); auto new_opt = admincmd_ptr->add_option_str(); new_opt->set_key(key); - new_opt->set_value(value); + if (option == opt_drivename_cmd && value == "first") { + new_opt->set_value(getDriveFromTpConfig()); + } else { + new_opt->set_value(value); + } break; } case Option::OPT_STR_LIST: { diff --git a/cmdline/CtaAdminCmd.hpp b/cmdline/CtaAdminCmd.hpp index fa5bfee36f7be15aa64a1c59f455859a93381966..3c53f016f0551d242c1314599780895fb4ce9fe5 100644 --- a/cmdline/CtaAdminCmd.hpp +++ b/cmdline/CtaAdminCmd.hpp @@ -49,6 +49,9 @@ private: //! Add a valid option to the protocol buffer void addOption(const Option &option, const std::string &value); + //! Parses the TPCONFIG file to obtain the first drive in the tapeserver + std::string getDriveFromTpConfig(); + //! Read a list of string options from a file void readListFromFile(cta::admin::OptionStrList &str_list, const std::string &filename); diff --git a/cmdline/CtaAdminCmdParse.hpp b/cmdline/CtaAdminCmdParse.hpp index 3d4d1a377989c2e06e943eb6e4330d4cf8517dcf..51bb8cc7b9f9bc5182e9211f351eec4b748b529e 100644 --- a/cmdline/CtaAdminCmdParse.hpp +++ b/cmdline/CtaAdminCmdParse.hpp @@ -66,6 +66,13 @@ public: return option == m_short_opt || option == m_long_opt; } + /*! + * Check if the supplied option matches the option + */ + bool operator==(const Option &option) const { + return option == m_short_opt || option == m_long_opt; + } + /*! * Return the type of this option */ @@ -238,6 +245,7 @@ const subcmdLookup_t subcmdLookup = { { "rm", AdminCmd::SUBCMD_RM }, { "up", AdminCmd::SUBCMD_UP }, { "down", AdminCmd::SUBCMD_DOWN }, + { "restore", AdminCmd::SUBCMD_RESTORE } }; @@ -345,7 +353,9 @@ const std::map<AdminCmd::Cmd, CmdHelp> cmdHelp = { "\n This is a synchronous command that sets and reads back the state of one or\n" " more drives. The <drive_name> option accepts a regular expression. If the\n" " --force option is not set, the drives will complete any running mount and\n" - " drives must be in the down state before deleting.\n\n" + " drives must be in the down state before deleting. If the <drive_name> option\n" + " is set to first, the up, down, ls and ch commands will use the first drive\n" + " listed in TPCONFIG\n\n" }}, { AdminCmd::CMD_FAILEDREQUEST, { "failedrequest", "fr", { "ls", "rm" } }}, { AdminCmd::CMD_GROUPMOUNTRULE, { "groupmountrule", "gmr", { "add", "ch", "rm", "ls" } }}, @@ -409,9 +419,11 @@ const std::map<AdminCmd::Cmd, CmdHelp> cmdHelp = { }}, { AdminCmd::CMD_VERSION, { "version", "v", { } }}, { AdminCmd::CMD_SCHEDULINGINFOS, { "schedulinginfo", "si", { "ls" } }}, - { AdminCmd::CMD_RECYCLETAPEFILE, { "recycletf", "rtf", { "ls" }, + { AdminCmd::CMD_RECYCLETAPEFILE, { "recycletf", "rtf", { "ls", "restore" }, + " This command allows to manage files in the recycle log.\n" " Tape files in the recycle log can be listed by VID, EOS disk file ID, EOS disk instance, ArchiveFileId or copy number.\n" - " Disk file IDs should be provided in hexadecimal (fxid).\n\n" }}, + " Disk file IDs should be provided in hexadecimal (fxid).\n" + " Deleted files can be restored with the restore command\n\n" }}, }; @@ -429,7 +441,6 @@ const Option opt_comment { Option::OPT_STR, "--comment", const Option opt_copynb { Option::OPT_UINT, "--copynb", "-c", " <copy_number>" }; const Option opt_copynb_alias { Option::OPT_UINT, "--numberofcopies", "-c", " <number_of_copies>", "--copynb" }; const Option opt_disabled { Option::OPT_BOOL, "--disabled", "-d", " <\"true\" or \"false\">" }; -const Option opt_drivename { Option::OPT_STR, "--drive", "-d", " <drive_name>" }; const Option opt_drivename_cmd { Option::OPT_CMD, "--drive", "", "<drive_name>" }; const Option opt_encrypted { Option::OPT_BOOL, "--encrypted", "-e", " <\"true\" or \"false\">" }; const Option opt_encryptionkeyname { Option::OPT_STR, "--encryptionkeyname", "-k", " <encryption_key_name>" }; @@ -508,8 +519,8 @@ const std::map<cmd_key_t, cmd_val_t> cmdOptions = { /*----------------------------------------------------------------------------------------------------*/ {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_UP }, { opt_drivename_cmd, opt_reason.optional() }}, {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_DOWN }, { opt_drivename_cmd, opt_reason, opt_force_flag.optional() }}, - {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_LS }, { opt_drivename.optional() }}, - {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_RM }, { opt_drivename_cmd, opt_force_flag.optional() }}, + {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_LS }, { opt_drivename_cmd.optional() }}, + {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_RM }, { opt_drivename_cmd, opt_force_flag.optional()}}, {{ AdminCmd::CMD_DRIVE, AdminCmd::SUBCMD_CH }, { opt_drivename_cmd, opt_comment }}, /*----------------------------------------------------------------------------------------------------*/ {{ AdminCmd::CMD_FAILEDREQUEST, AdminCmd::SUBCMD_LS }, @@ -620,6 +631,8 @@ const std::map<cmd_key_t, cmd_val_t> cmdOptions = { {{ AdminCmd::CMD_SCHEDULINGINFOS, AdminCmd::SUBCMD_LS }, { }}, {{ AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_LS }, { opt_vid.optional(), opt_fid.optional(), opt_fidfile.optional(), opt_copynb.optional(), opt_archivefileid.optional(), opt_instance.optional() }}, + {{ AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_RESTORE }, + { opt_vid.optional(), opt_fid.optional(), opt_fidfile.optional(), opt_copynb.optional(), opt_archivefileid.optional(), opt_instance.optional() }}, }; @@ -630,5 +643,4 @@ const std::map<cmd_key_t, cmd_val_t> cmdOptions = { * Throws a std::runtime_error if the command is invalid */ void validateCmd(const cta::admin::AdminCmd &admincmd); - }} // namespace cta::admin diff --git a/xroot_plugins/XrdCtaTapeFileLs.hpp b/xroot_plugins/XrdCtaTapeFileLs.hpp index a90582ebfa3419cea7e735ed6071fdce597063ca..ca813935ca409d01424f53514348b619e70aba8a 100644 --- a/xroot_plugins/XrdCtaTapeFileLs.hpp +++ b/xroot_plugins/XrdCtaTapeFileLs.hpp @@ -94,8 +94,7 @@ TapeFileLsStream::TapeFileLsStream(const RequestMessage &requestMsg, } searchCriteria.diskFileIds->push_back(std::to_string(fid)); } - // Disk instance on its own does not give a valid set of search criteria (no &has_any) - searchCriteria.diskInstance = requestMsg.getOptional(OptionString::INSTANCE); + searchCriteria.diskInstance = requestMsg.getOptional(OptionString::INSTANCE, &has_any); searchCriteria.archiveFileId = requestMsg.getOptional(OptionUInt64::ARCHIVE_FILE_ID, &has_any); diff --git a/xroot_plugins/XrdSsiCtaRequestMessage.cpp b/xroot_plugins/XrdSsiCtaRequestMessage.cpp index 43a403e1142e827bf245b998b376bf2874ec6a97..265c4a048e6bfc85c381a97407d8eb0d44ae5514 100644 --- a/xroot_plugins/XrdSsiCtaRequestMessage.cpp +++ b/xroot_plugins/XrdSsiCtaRequestMessage.cpp @@ -282,6 +282,10 @@ void RequestMessage::process(const cta::xrd::Request &request, cta::xrd::Respons case cmd_pair(AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_LS): processRecycleTapeFile_Ls(response,stream); break; + case cmd_pair(AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_RESTORE): + processRecycleTapeFile_Restore(response); + break; + default: throw PbException("Admin command pair <" + AdminCmd_Cmd_Name(request.admincmd().cmd()) + ", " + @@ -2146,4 +2150,39 @@ void RequestMessage::processRecycleTapeFile_Ls(cta::xrd::Response &response, Xrd response.set_type(cta::xrd::Response::RSP_SUCCESS); } +void RequestMessage::processRecycleTapeFile_Restore(cta::xrd::Response& response) { + response.set_type(cta::xrd::Response::RSP_SUCCESS); + using namespace cta::admin; + + bool has_any = false; + cta::catalogue::RecycleTapeFileSearchCriteria searchCriteria; + + searchCriteria.vid = getOptional(OptionString::VID, &has_any); + auto diskFileId = getOptional(OptionString::FXID, &has_any); + searchCriteria.diskFileIds = getOptional(OptionStrList::FILE_ID, &has_any); + + if(diskFileId){ + // single option on the command line we need to do the conversion ourselves. + if(!searchCriteria.diskFileIds) searchCriteria.diskFileIds = std::vector<std::string>(); + + auto fid = strtol(diskFileId->c_str(), nullptr, 16); + if(fid < 1 || fid == LONG_MAX) { + throw cta::exception::UserError(*diskFileId + " is not a valid file ID"); + } + + searchCriteria.diskFileIds->push_back(std::to_string(fid)); + } + // Disk instance on its own does not give a valid set of search criteria (no &has_any) + searchCriteria.diskInstance = getOptional(OptionString::INSTANCE); + searchCriteria.archiveFileId = getOptional(OptionUInt64::ARCHIVE_FILE_ID, &has_any); + // Copy number on its own does not give a valid set of search criteria (no &has_any) + searchCriteria.copynb = getOptional(OptionUInt64::COPY_NUMBER); + + if(!has_any){ + throw cta::exception::UserError("Must specify at least one search option"); + } + m_catalogue.restoreFilesInRecycleLog(searchCriteria); + response.set_type(cta::xrd::Response::RSP_SUCCESS); +} + }} // namespace cta::xrd diff --git a/xroot_plugins/XrdSsiCtaRequestMessage.hpp b/xroot_plugins/XrdSsiCtaRequestMessage.hpp index fb9b2f4f27b2eda48cb1a1db3992f2010c1c35e3..616516bcffb2c4851334ba0f580e9f2f2388eaca 100644 --- a/xroot_plugins/XrdSsiCtaRequestMessage.hpp +++ b/xroot_plugins/XrdSsiCtaRequestMessage.hpp @@ -208,6 +208,7 @@ private: void processVirtualOrganization_Add(cta::xrd::Response &response); void processVirtualOrganization_Ch(cta::xrd::Response &response); void processVirtualOrganization_Rm(cta::xrd::Response &response); + void processRecycleTapeFile_Restore(cta::xrd::Response &response); /*! * Process AdminCmd events which can return a stream response diff --git a/xrootd-ssi-protobuf-interface b/xrootd-ssi-protobuf-interface index 2f8068955f6622f26e6373a70cfcb4f16435d7ea..3ebc433ff481ebf8fc8cfcaf8dc3dff608fceee0 160000 --- a/xrootd-ssi-protobuf-interface +++ b/xrootd-ssi-protobuf-interface @@ -1 +1 @@ -Subproject commit 2f8068955f6622f26e6373a70cfcb4f16435d7ea +Subproject commit 3ebc433ff481ebf8fc8cfcaf8dc3dff608fceee0