diff --git a/catalogue/CMakeLists.txt b/catalogue/CMakeLists.txt index d43302c42586fd30d5ed1f0e85f8f9329259a5ac..3e6cd3b5176bf653d2ef4c647b15a93f389b245d 100644 --- a/catalogue/CMakeLists.txt +++ b/catalogue/CMakeLists.txt @@ -60,8 +60,15 @@ set (CATALOGUE_LIB_SRC_FILES UserSpecifiedANonEmptyTape.cpp UserSpecifiedANonExistentLogicalLibrary.cpp UserSpecifiedANonExistentTape.cpp + UserSpecifiedANonExistentDiskSystem.cpp + UserSpecifiedANonEmptyDiskSystemAfterDelete.cpp UserSpecifiedAnEmptyStringComment.cpp UserSpecifiedAnEmptyStringDiskInstanceName.cpp + UserSpecifiedAnEmptyStringDiskSystemName.cpp + UserSpecifiedAnEmptyStringFileRegexp.cpp + UserSpecifiedAnEmptyStringFreeSpaceQueryURL.cpp + UserSpecifiedAZeroRefreshInterval.cpp + UserSpecifiedAZeroTargetedFreeSpace.cpp UserSpecifiedAnEmptyStringLogicalLibraryName.cpp UserSpecifiedAnEmptyStringMediaType.cpp UserSpecifiedAnEmptyStringStorageClassName.cpp @@ -171,7 +178,7 @@ set_property(TARGET ctainmemorycatalogueunittests PROPERTY SOVERSION "${CTA_SOVE set_property(TARGET ctainmemorycatalogueunittests PROPERTY VERSION "${CTA_LIBVERSION}") target_link_libraries (ctainmemorycatalogueunittests - ctacatalogue) + ctacatalogue ctadisk) install (TARGETS ctainmemorycatalogueunittests DESTINATION usr/${CMAKE_INSTALL_LIBDIR}) diff --git a/catalogue/Catalogue.hpp b/catalogue/Catalogue.hpp index 305f75ce995cb64863e0d80d67ff819164273a0c..5424fa3d859879221e0d7a052d2ff07db9ad1cf6 100644 --- a/catalogue/Catalogue.hpp +++ b/catalogue/Catalogue.hpp @@ -55,6 +55,7 @@ #include "common/dataStructures/RequesterIdentity.hpp" #include "common/dataStructures/VidToTapeMap.hpp" #include "common/dataStructures/WriteTestResult.hpp" +#include "disk/DiskSystem.hpp" #include "common/exception/FileSizeMismatch.hpp" #include "common/exception/TapeFseqMismatch.hpp" #include "common/exception/UserError.hpp" @@ -541,6 +542,60 @@ public: virtual void deleteActivitiesFairShareWeight(const common::dataStructures::SecurityIdentity &admin, const std::string & diskInstanceName, const std::string & acttivity) = 0; virtual std::list<common::dataStructures::ActivitiesFairShareWeights> getActivitiesFairShareWeights() const = 0; + /** + * Returns all the disk systems within the CTA catalogue. + * + * @return The disk systems. + * requester group. + */ + virtual disk::DiskSystemList getAllDiskSystems() const = 0; + + /** + * Creates a disk system. + * + * @param admin The administrator. + * @param name The name of the disk system. + * @param fileRegexp The regular expression allowing matching destination URLs + * for this disk system. + * @param freeSpaceQueryURL The query URL that describes a method to query the + * free space from the disk system. + * @param refreshInterval The refresh interval (seconds) defining how long do + * we use a free space value. + * @param targetedFreeSpace The targeted free space (margin) based on the free + * space update latency (inherent to the file system and induced by the refresh + * interval), and the expected external bandwidth from sources external to CTA. + * @param comment Comment. + */ + virtual void createDiskSystem( + const common::dataStructures::SecurityIdentity &admin, + const std::string &name, + const std::string &fileRegexp, + const std::string &freeSpaceQueryURL, + const uint64_t refreshInterval, + const uint64_t targetedFreeSpace, + const uint64_t sleepTime, + const std::string &comment) = 0; + + /** + * Deletes a disk system. + * + * @param name The name of the disk system. + */ + virtual void deleteDiskSystem(const std::string &name) = 0; + + virtual void modifyDiskSystemFileRegexp(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &fileRegexp) = 0; + virtual void modifyDiskSystemFreeSpaceQueryURL(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &freeSpaceQueryURL) = 0; + virtual void modifyDiskSystemRefreshInterval(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t refreshInterval) = 0; + virtual void modifyDiskSystemTargetedFreeSpace(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t targetedFreeSpace) = 0; + virtual void modifyDiskSystemSleepTime(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t sleepTime) = 0; + virtual void modifyDiskSystemComment(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &comment) = 0; + /** * Returns the specified archive files. Please note that the list of files * is ordered by archive file ID. @@ -659,11 +714,20 @@ public: * @return True if the tape exists. */ virtual bool tapeExists(const std::string &vid) const = 0; + + /** + * Returns true if the specified disk system exists. + * + * @param name The name identifier of the disk system. + * @return True if the tape exists. + */ + virtual bool diskSystemExists(const std::string &name) const = 0; }; // class Catalogue CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedAnEmptyStringActivity); CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedAnOutOfRangeActivityWeight); +CTA_GENERATE_USER_EXCEPTION_CLASS(UserSpecifiedAZeroSleepTime); } // namespace catalogue } // namespace cta diff --git a/catalogue/CatalogueRetryWrapper.hpp b/catalogue/CatalogueRetryWrapper.hpp index 4e78ae7a54f301030ec8408dfb9dca749e8e8c08..73797d6a3fe197ee565c89febd4d92b3217b45cc 100644 --- a/catalogue/CatalogueRetryWrapper.hpp +++ b/catalogue/CatalogueRetryWrapper.hpp @@ -381,7 +381,42 @@ public: return retryOnLostConnection(m_log, [&]{return m_catalogue->getActivitiesFairShareWeights();}, m_maxTriesToConnect); } + disk::DiskSystemList getAllDiskSystems() const override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->getAllDiskSystems();}, m_maxTriesToConnect); + } + + void createDiskSystem(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &fileRegexp, const std::string &freeSpaceQueryURL, const uint64_t refreshInterval, const uint64_t targetedFreeSpace, const uint64_t sleepTime, const std::string &comment) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->createDiskSystem(admin, name, fileRegexp, freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment);}, m_maxTriesToConnect); + } + + void deleteDiskSystem(const std::string &name) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->deleteDiskSystem(name);}, m_maxTriesToConnect); + } + + void modifyDiskSystemFileRegexp(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &fileRegexp) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemFileRegexp(admin, name, fileRegexp);}, m_maxTriesToConnect); + } + void modifyDiskSystemFreeSpaceQueryURL(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &freeSpaceQueryURL) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemFreeSpaceQueryURL(admin, name, freeSpaceQueryURL);}, m_maxTriesToConnect); + } + + void modifyDiskSystemRefreshInterval(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const uint64_t refreshInterval) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemRefreshInterval(admin, name, refreshInterval);}, m_maxTriesToConnect); + } + + void modifyDiskSystemTargetedFreeSpace(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const uint64_t targetedFreeSpace) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemTargetedFreeSpace(admin, name, targetedFreeSpace);}, m_maxTriesToConnect); + } + + void modifyDiskSystemComment(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &comment) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemComment(admin, name, comment);}, m_maxTriesToConnect); + } + + void modifyDiskSystemSleepTime(const common::dataStructures::SecurityIdentity& admin, const std::string& name, const uint64_t sleepTime) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyDiskSystemSleepTime(admin, name, sleepTime);}, m_maxTriesToConnect); + } + ArchiveFileItor getArchiveFilesItor(const TapeFileSearchCriteria &searchCriteria = TapeFileSearchCriteria()) const override { return retryOnLostConnection(m_log, [&]{return m_catalogue->getArchiveFilesItor(searchCriteria);}, m_maxTriesToConnect); } @@ -426,6 +461,10 @@ public: return retryOnLostConnection(m_log, [&]{return m_catalogue->tapeExists(vid);}, m_maxTriesToConnect); } + bool diskSystemExists(const std::string &name) const override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->diskSystemExists(name);}, m_maxTriesToConnect); + }; + protected: /** diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp index 9fcf23f773b1c6e9ed4fcef9a3829e439aadaada..106b23ad10f916372890284c312909fc0d350e59 100644 --- a/catalogue/CatalogueTest.cpp +++ b/catalogue/CatalogueTest.cpp @@ -22,6 +22,12 @@ #include "catalogue/UserSpecifiedANonEmptyTape.hpp" #include "catalogue/UserSpecifiedANonExistentLogicalLibrary.hpp" #include "catalogue/UserSpecifiedANonExistentTape.hpp" +#include "catalogue/UserSpecifiedANonExistentDiskSystem.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp" +#include "catalogue/UserSpecifiedAZeroRefreshInterval.hpp" +#include "catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp" #include "catalogue/UserSpecifiedAnEmptyStringComment.hpp" #include "catalogue/UserSpecifiedAnEmptyStringDiskInstanceName.hpp" #include "catalogue/UserSpecifiedAnEmptyStringLogicalLibraryName.hpp" @@ -169,6 +175,12 @@ void cta_catalogue_CatalogueTest::SetUp() { m_catalogue->deleteMountPolicy(mountPolicy.name); } } + { + const auto diskSystems = m_catalogue->getAllDiskSystems(); + for(auto &ds: diskSystems) { + m_catalogue->deleteDiskSystem(ds.name); + } + } } catch(exception::Exception &ex) { throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str()); } @@ -12532,6 +12544,925 @@ TEST_P(cta_catalogue_CatalogueTest, getAllTapes_many_tapes) { } } +TEST_P(cta_catalogue_CatalogueTest, getAllDiskSystems_no_systems) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); +} + +TEST_P(cta_catalogue_CatalogueTest, getAllDiskSystems_many_diskSystems) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_URL"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + + const uint32_t nbDiskSystems = 16; + + for(uint32_t i = 0; i < nbDiskSystems; i++) { + std::ostringstream name; + name << "DiskSystem" << std::setfill('0') << std::setw(5) << i; + const std::string diskSystemComment = "Create disk system " + name.str(); + m_catalogue->createDiskSystem(m_admin, name.str(), fileRegexp, + freeSpaceQueryURL, refreshInterval + i, targetedFreeSpace + i, sleepTime + i, diskSystemComment); + } + + auto diskSystemsList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(nbDiskSystems, diskSystemsList.size()); + + for(uint32_t i = 0; i < nbDiskSystems; i++) { + std::ostringstream name; + name << "DiskSystem" << std::setfill('0') << std::setw(5) << i; + const std::string diskSystemComment = "Create disk system " + name.str(); + ASSERT_NO_THROW(diskSystemsList.at(name.str())); + const auto diskSystem = diskSystemsList.at(name.str()); + + ASSERT_EQ(name.str(), diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval + i, diskSystem.refreshInterval ); + ASSERT_EQ(targetedFreeSpace + i, diskSystem.targetedFreeSpace); + ASSERT_EQ(diskSystemComment, diskSystem.comment); + } +} + +TEST_P(cta_catalogue_CatalogueTest, diskSystemExists_emptyString) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = ""; + + ASSERT_THROW(m_catalogue->diskSystemExists(name), exception::Exception); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = ""; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_URL"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "Create disk system"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_emptyStringFileRegexp) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = ""; + const std::string freeSpaceQueryURL = "free_space_query_URL"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "Create disk system"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAnEmptyStringFileRegexp); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_emptyStringFresSpaceQueryURL) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = ""; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "Create disk system"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAnEmptyStringFreeSpaceQueryURL); +} + + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_zeroRefreshInterval) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 0; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "Create disk system"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAZeroRefreshInterval); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_zeroTargetedFreeSpace) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 0; + const uint64_t sleepTime = 15*60; + const std::string comment = "Create disk system"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAZeroTargetedFreeSpace); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_emptyStringComment) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = ""; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAnEmptyStringComment); +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_9_exabytes_targetedFreeSpace) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + // The maximum size of an SQLite integer is a signed 64-bit integer + const uint64_t targetedFreeSpace = 9L * 1000 * 1000 * 1000 * 1000 * 1000 * 1000; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + + ASSERT_EQ(1, diskSystemList.size()); + + { + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(sleepTime, diskSystem.sleepTime); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } +} + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_sleepTimeHandling) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 0; + const std::string comment = "disk system comment"; + + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), + catalogue::UserSpecifiedAZeroSleepTime); + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, std::numeric_limits<int64_t>::max(), comment); + + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + + ASSERT_EQ(1, diskSystemList.size()); + + { + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(std::numeric_limits<int64_t>::max(), diskSystem.sleepTime); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } +} + + +TEST_P(cta_catalogue_CatalogueTest, createDiskSystem_same_twice) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + + ASSERT_EQ(1, diskSystemList.size()); + ASSERT_THROW(m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment), exception::UserError); + +} + +TEST_P(cta_catalogue_CatalogueTest, deleteDiskSystem) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + + m_catalogue->deleteDiskSystem(diskSystem.name); + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); +} + +TEST_P(cta_catalogue_CatalogueTest, deleteDiskSystem_non_existant) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + ASSERT_THROW(m_catalogue->deleteDiskSystem("non_exsitant_disk_system"), catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFileRegexp) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedFileRegexp = "modified_fileRegexp"; + m_catalogue->modifyDiskSystemFileRegexp(m_admin, name, modifiedFileRegexp); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(modifiedFileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFileRegexp_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = ""; + const std::string modifiedFileRegexp = "modified_fileRegexp"; + ASSERT_THROW(m_catalogue->modifyDiskSystemFileRegexp(m_admin, diskSystemName, modifiedFileRegexp), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFileRegexp_nonExistentDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = "dummyDiskSystemName"; + const std::string modifiedFileRegexp = "modified_fileRegexp"; + ASSERT_THROW(m_catalogue->modifyDiskSystemFileRegexp(m_admin, diskSystemName, modifiedFileRegexp), + catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFileRegexp_emptyStringFileRegexp) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedFileRegexp = ""; + ASSERT_THROW(m_catalogue->modifyDiskSystemFileRegexp(m_admin, name, modifiedFileRegexp), + catalogue::UserSpecifiedAnEmptyStringFileRegexp); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFreeSpaceQueryURL) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedFreeSpaceQueryURL = "modified_freeSpaceQueryURL"; + m_catalogue->modifyDiskSystemFreeSpaceQueryURL(m_admin, name, modifiedFreeSpaceQueryURL); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(modifiedFreeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFreeSpaceQueryURL_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = ""; + const std::string modifiedFreeSpaceQueryURL = "modified_freeSpaceQueryURL"; + ASSERT_THROW(m_catalogue->modifyDiskSystemFreeSpaceQueryURL(m_admin, diskSystemName, modifiedFreeSpaceQueryURL), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFreeSpaceQueryURL_nonExistentDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = "dummyDiskSystemName"; + const std::string modifiedFreeSpaceQueryURL = "modified_freeSpaceQueryURL"; + ASSERT_THROW(m_catalogue->modifyDiskSystemFreeSpaceQueryURL(m_admin, diskSystemName, modifiedFreeSpaceQueryURL), + catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemFreeSpaceQueryURL_emptyStringFreeSpaceQueryURL) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedFreeSpaceQueryURL = ""; + ASSERT_THROW(m_catalogue->modifyDiskSystemFreeSpaceQueryURL(m_admin, name, modifiedFreeSpaceQueryURL), + catalogue::UserSpecifiedAnEmptyStringFreeSpaceQueryURL); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemRefreshInterval) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const uint64_t modifiedRefreshInterval = 128; + m_catalogue->modifyDiskSystemRefreshInterval(m_admin, name, modifiedRefreshInterval); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(modifiedRefreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemRefreshInterval_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = ""; + const uint64_t modifiedRefreshInterval = 128; + ASSERT_THROW(m_catalogue->modifyDiskSystemRefreshInterval(m_admin, diskSystemName, modifiedRefreshInterval), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemRefreshInterval_nonExistentDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = "dummyDiskSystemName"; + const uint64_t modifiedRefreshInterval = 128; + ASSERT_THROW(m_catalogue->modifyDiskSystemRefreshInterval(m_admin, diskSystemName, modifiedRefreshInterval), + catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemRefreshInterval_zeroRefreshInterval) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const uint64_t modifiedRefreshInterval = 0; + ASSERT_THROW(m_catalogue->modifyDiskSystemRefreshInterval(m_admin, name, modifiedRefreshInterval), + catalogue::UserSpecifiedAZeroRefreshInterval); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemTargetedFreeSpace) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const uint64_t modifiedTargetedFreeSpace = 128; + m_catalogue->modifyDiskSystemTargetedFreeSpace(m_admin, name, modifiedTargetedFreeSpace); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(modifiedTargetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemTargetedFreeSpace_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = ""; + const uint64_t modifiedTargetedFreeSpace = 128; + ASSERT_THROW(m_catalogue->modifyDiskSystemTargetedFreeSpace(m_admin, diskSystemName, modifiedTargetedFreeSpace), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemTargetedFreeSpace_nonExistentDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = "dummyDiskSystemName"; + const uint64_t modifiedTargetedFreeSpace = 128; + ASSERT_THROW(m_catalogue->modifyDiskSystemTargetedFreeSpace(m_admin, diskSystemName, modifiedTargetedFreeSpace), + catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemTargetedFreeSpace_zeroTargetedFreeSpace) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const uint64_t modifiedTargetedFreeSpace = 0; + ASSERT_THROW(m_catalogue->modifyDiskSystemTargetedFreeSpace(m_admin, name, modifiedTargetedFreeSpace), + catalogue::UserSpecifiedAZeroTargetedFreeSpace); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemComment) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedComment = "modified_comment"; + m_catalogue->modifyDiskSystemComment(m_admin, name, modifiedComment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(modifiedComment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemComment_emptyStringDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = ""; + const std::string modifiedComment = "modified_comment"; + ASSERT_THROW(m_catalogue->modifyDiskSystemComment(m_admin, diskSystemName, modifiedComment), + catalogue::UserSpecifiedAnEmptyStringDiskSystemName); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemComment_nonExistentDiskSystemName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string diskSystemName = "dummyDiskSystemName"; + const std::string modifiedComment = "modified_comment"; + ASSERT_THROW(m_catalogue->modifyDiskSystemComment(m_admin, diskSystemName, modifiedComment), + catalogue::UserSpecifiedANonExistentDiskSystem); +} + +TEST_P(cta_catalogue_CatalogueTest, modifyDiskSystemCommentL_emptyStringComment) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getAllDiskSystems().empty()); + + const std::string name = "disk_system_name"; + const std::string fileRegexp = "file_regexp"; + const std::string freeSpaceQueryURL = "free_space_query_url"; + const uint64_t refreshInterval = 32; + const uint64_t targetedFreeSpace = 64; + const uint64_t sleepTime = 15*60; + const std::string comment = "disk system comment"; + + m_catalogue->createDiskSystem(m_admin, name, fileRegexp, + freeSpaceQueryURL, refreshInterval, targetedFreeSpace, sleepTime, comment); + + { + const auto diskSystemList = m_catalogue->getAllDiskSystems(); + ASSERT_EQ(1, diskSystemList.size()); + + const auto &diskSystem = diskSystemList.front(); + ASSERT_EQ(name, diskSystem.name); + ASSERT_EQ(fileRegexp, diskSystem.fileRegexp); + ASSERT_EQ(freeSpaceQueryURL, diskSystem.freeSpaceQueryURL); + ASSERT_EQ(refreshInterval, diskSystem.refreshInterval); + ASSERT_EQ(targetedFreeSpace, diskSystem.targetedFreeSpace); + ASSERT_EQ(comment, diskSystem.comment); + + const auto creationLog = diskSystem.creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const auto lastModificationLog = diskSystem.lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string modifiedComment = ""; + ASSERT_THROW(m_catalogue->modifyDiskSystemComment(m_admin, name, modifiedComment), + catalogue::UserSpecifiedAnEmptyStringComment); +} + TEST_P(cta_catalogue_CatalogueTest, getNbFilesOnTape_no_tape_files) { using namespace cta; diff --git a/catalogue/DropSchemaCmd.cpp b/catalogue/DropSchemaCmd.cpp index b154bbd33f08db29941082feb147872bbcb58304..40bf7fa116cf9703908ad7718043f1d99623cb11 100644 --- a/catalogue/DropSchemaCmd.cpp +++ b/catalogue/DropSchemaCmd.cpp @@ -149,7 +149,8 @@ void DropSchemaCmd::dropSqliteCatalogueSchema(rdbms::Conn &conn) { "MOUNT_POLICY", "ACTIVITIES_WEIGHTS", "USAGESTATS", - "EXPERIMENTS" + "EXPERIMENTS", + "DISK_SYSTEM" }; dropDatabaseTables(conn, tablesToDrop); } catch(exception::Exception &ex) { @@ -180,7 +181,8 @@ void DropSchemaCmd::dropMysqlCatalogueSchema(rdbms::Conn &conn) { "MOUNT_POLICY", "ACTIVITIES_WEIGHTS", "USAGESTATS", - "EXPERIMENTS" + "EXPERIMENTS", + "DISK_SYSTEM" }; dropDatabaseTables(conn, tablesToDrop); @@ -248,7 +250,8 @@ void DropSchemaCmd::dropOracleCatalogueSchema(rdbms::Conn &conn) { "MOUNT_POLICY", "ACTIVITIES_WEIGHTS", "USAGESTATS", - "EXPERIMENTS" + "EXPERIMENTS", + "DISK_SYSTEM" }; dropDatabaseTables(conn, tablesToDrop); @@ -282,7 +285,8 @@ void DropSchemaCmd::dropPostgresCatalogueSchema(rdbms::Conn &conn) { "MOUNT_POLICY", "ACTIVITIES_WEIGHTS", "USAGESTATS", - "EXPERIMENTS" + "EXPERIMENTS", + "DISK_SYSTEM" }; dropDatabaseTables(conn, tablesToDrop); diff --git a/catalogue/DummyCatalogue.hpp b/catalogue/DummyCatalogue.hpp index b314e32ed2e8d9b9f30ff534970a3cf8c9d5076e..4faf50c85f9a151d491128c8dfa6083006659c88 100644 --- a/catalogue/DummyCatalogue.hpp +++ b/catalogue/DummyCatalogue.hpp @@ -57,7 +57,16 @@ public: void deleteTapePool(const std::string& name) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void filesWrittenToTape(const std::set<TapeItemWrittenPointer>& event) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } std::list<common::dataStructures::ActivitiesFairShareWeights> getActivitiesFairShareWeights() const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } - std::list<common::dataStructures::AdminUser> getAdminUsers() const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + disk::DiskSystemList getAllDiskSystems() const override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + void createDiskSystem(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &fileRegexp, const std::string &freeSpaceQueryURL, const uint64_t refreshInterval, const uint64_t targetedFreeSpace, const uint64_t sleepTime, const std::string &comment) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + void deleteDiskSystem(const std::string &name) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + void modifyDiskSystemFileRegexp(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &fileRegexp) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + void modifyDiskSystemFreeSpaceQueryURL(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &freeSpaceQueryURL) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + void modifyDiskSystemRefreshInterval(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const uint64_t refreshInterval) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + void modifyDiskSystemTargetedFreeSpace(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const uint64_t targetedFreeSpace) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + void modifyDiskSystemSleepTime(const common::dataStructures::SecurityIdentity& admin, const std::string& name, const uint64_t sleepTime) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + void modifyDiskSystemComment(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &comment) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented");} + std::list<common::dataStructures::AdminUser> getAdminUsers() const override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } common::dataStructures::ArchiveFile getArchiveFileById(const uint64_t id) 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"); } 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"); } @@ -121,6 +130,7 @@ public: void setTapeIsFromCastorInUnitTests(const std::string &vid) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void setTapePoolEncryption(const common::dataStructures::SecurityIdentity& admin, const std::string& name, const bool encryptionValue) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } bool tapeExists(const std::string& vid) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + bool diskSystemExists(const std::string& name) const { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void tapeLabelled(const std::string& vid, const std::string& drive) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void tapeMountedForArchive(const std::string& vid, const std::string& drive) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void tapeMountedForRetrieve(const std::string& vid, const std::string& drive) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp index 663a2c33d6809c01991d3d8757d8ffce7457416e..ca64d317fcc403da9b999435abb4b08dfcf6562e 100644 --- a/catalogue/RdbmsCatalogue.cpp +++ b/catalogue/RdbmsCatalogue.cpp @@ -22,11 +22,18 @@ #include "catalogue/RdbmsCatalogueGetArchiveFilesForRepackItor.hpp" #include "catalogue/retryOnLostConnection.hpp" #include "catalogue/SqliteCatalogueSchema.hpp" +#include "catalogue/UserSpecifiedANonExistentDiskSystem.hpp" +#include "catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.hpp" #include "catalogue/UserSpecifiedANonEmptyLogicalLibrary.hpp" #include "catalogue/UserSpecifiedANonEmptyTape.hpp" #include "catalogue/UserSpecifiedANonExistentLogicalLibrary.hpp" #include "catalogue/UserSpecifiedANonExistentTape.hpp" #include "catalogue/UserSpecifiedAnEmptyStringComment.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp" +#include "catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp" +#include "catalogue/UserSpecifiedAZeroRefreshInterval.hpp" +#include "catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp" #include "catalogue/UserSpecifiedAnEmptyStringDiskInstanceName.hpp" #include "catalogue/UserSpecifiedAnEmptyStringLogicalLibraryName.hpp" #include "catalogue/UserSpecifiedAnEmptyStringMediaType.hpp" @@ -2004,6 +2011,45 @@ bool RdbmsCatalogue::tapeExists(rdbms::Conn &conn, const std::string &vid) const } } +//------------------------------------------------------------------------------ +// diskSystemExists +//------------------------------------------------------------------------------ +bool RdbmsCatalogue::diskSystemExists(const std::string &name) const { + try { + auto conn = m_connPool.getConn(); + return diskSystemExists(conn, name); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// diskSystemExists +//------------------------------------------------------------------------------ +bool RdbmsCatalogue::diskSystemExists(rdbms::Conn &conn, const std::string &name) const { + try { + const char *const sql = + "SELECT " + "DISK_SYSTEM_NAME AS DISK_SYSTEM_NAME " + "FROM " + "DISK_SYSTEM " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto stmt = conn.createStmt(sql); + stmt.bindString(":DISK_SYSTEM_NAME", name); + auto rset = stmt.executeQuery(); + return rset.next(); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // deleteTape //------------------------------------------------------------------------------ @@ -4637,6 +4683,450 @@ std::list<common::dataStructures::ActivitiesFairShareWeights> RdbmsCatalogue::ge } } +//------------------------------------------------------------------------------ +// getAllDiskSystems +//------------------------------------------------------------------------------ +disk::DiskSystemList RdbmsCatalogue::getAllDiskSystems() const { + try { + disk::DiskSystemList diskSystemList; + std::string sql = + "SELECT " + "DISK_SYSTEM.DISK_SYSTEM_NAME AS DISK_SYSTEM_NAME," + "DISK_SYSTEM.FILE_REGEXP AS FILE_REGEXP," + "DISK_SYSTEM.FREE_SPACE_QUERY_URL AS FREE_SPACE_QUERY_URL," + "DISK_SYSTEM.REFRESH_INTERVAL AS REFRESH_INTERVAL," + "DISK_SYSTEM.TARGETED_FREE_SPACE AS TARGETED_FREE_SPACE," + "DISK_SYSTEM.SLEEP_TIME AS SLEEP_TIME," + + "DISK_SYSTEM.USER_COMMENT AS USER_COMMENT," + + "DISK_SYSTEM.CREATION_LOG_USER_NAME AS CREATION_LOG_USER_NAME," + "DISK_SYSTEM.CREATION_LOG_HOST_NAME AS CREATION_LOG_HOST_NAME," + "DISK_SYSTEM.CREATION_LOG_TIME AS CREATION_LOG_TIME," + + "DISK_SYSTEM.LAST_UPDATE_USER_NAME AS LAST_UPDATE_USER_NAME," + "DISK_SYSTEM.LAST_UPDATE_HOST_NAME AS LAST_UPDATE_HOST_NAME," + "DISK_SYSTEM.LAST_UPDATE_TIME AS LAST_UPDATE_TIME " + "FROM " + "DISK_SYSTEM"; + + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + + auto rset = stmt.executeQuery(); + while (rset.next()) { + disk::DiskSystem diskSystem; + diskSystem.name = rset.columnString("DISK_SYSTEM_NAME"); + diskSystem.fileRegexp = rset.columnString("FILE_REGEXP"); + diskSystem.freeSpaceQueryURL = rset.columnString("FREE_SPACE_QUERY_URL"); + diskSystem.refreshInterval = rset.columnUint64("REFRESH_INTERVAL"); + diskSystem.targetedFreeSpace = rset.columnUint64("TARGETED_FREE_SPACE"); + diskSystem.sleepTime = rset.columnUint64("SLEEP_TIME"); + diskSystem.comment = rset.columnString("USER_COMMENT"); + diskSystem.creationLog.username = rset.columnString("CREATION_LOG_USER_NAME"); + diskSystem.creationLog.host = rset.columnString("CREATION_LOG_HOST_NAME"); + diskSystem.creationLog.time = rset.columnUint64("CREATION_LOG_TIME"); + diskSystem.lastModificationLog.username = rset.columnString("LAST_UPDATE_USER_NAME"); + diskSystem.lastModificationLog.host = rset.columnString("LAST_UPDATE_HOST_NAME"); + diskSystem.lastModificationLog.time = rset.columnUint64("LAST_UPDATE_TIME"); + diskSystemList.push_back(diskSystem); + } + return diskSystemList; + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// createDiskSystem +//------------------------------------------------------------------------------ +void RdbmsCatalogue::createDiskSystem( + const common::dataStructures::SecurityIdentity &admin, + const std::string &name, + const std::string &fileRegexp, + const std::string &freeSpaceQueryURL, + const uint64_t refreshInterval, + const uint64_t targetedFreeSpace, + const uint64_t sleepTime, + const std::string &comment) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot create disk system because the name is an empty string"); + } + if(fileRegexp.empty()) { + throw UserSpecifiedAnEmptyStringFileRegexp("Cannot create disk system because the file regexp is an empty string"); + } + if(freeSpaceQueryURL.empty()) { + throw UserSpecifiedAnEmptyStringFreeSpaceQueryURL("Cannot create disk system because the free space query URL is an empty string"); + } + if(0 == refreshInterval) { + throw UserSpecifiedAZeroRefreshInterval("Cannot create disk system because the refresh interval is zero"); + } + if(0 == targetedFreeSpace) { + throw UserSpecifiedAZeroTargetedFreeSpace("Cannot create disk system because the targeted free space is zero"); + } + if (0 == sleepTime) { + throw UserSpecifiedAZeroSleepTime("Cannot create disk system because the sleep time is zero"); + } + if(comment.empty()) { + throw UserSpecifiedAnEmptyStringComment("Cannot create disk system because the comment is an empty string"); + } + + auto conn = m_connPool.getConn(); + if(diskSystemExists(conn, name)) { + throw exception::UserError(std::string("Cannot create disk system ") + name + + " because a disk system with the same name identifier already exists"); + } + + const time_t now = time(nullptr); + const char *const sql = + "INSERT INTO DISK_SYSTEM(" + "DISK_SYSTEM_NAME," + "FILE_REGEXP," + "FREE_SPACE_QUERY_URL," + "REFRESH_INTERVAL," + "TARGETED_FREE_SPACE," + "SLEEP_TIME," + + "USER_COMMENT," + + "CREATION_LOG_USER_NAME," + "CREATION_LOG_HOST_NAME," + "CREATION_LOG_TIME," + + "LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME)" + "VALUES(" + ":DISK_SYSTEM_NAME," + ":FILE_REGEXP," + ":FREE_SPACE_QUERY_URL," + ":REFRESH_INTERVAL," + ":TARGETED_FREE_SPACE," + ":SLEEP_TIME," + + ":USER_COMMENT," + + ":CREATION_LOG_USER_NAME," + ":CREATION_LOG_HOST_NAME," + ":CREATION_LOG_TIME," + + ":LAST_UPDATE_USER_NAME," + ":LAST_UPDATE_HOST_NAME," + ":LAST_UPDATE_TIME)"; + auto stmt = conn.createStmt(sql); + + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.bindString(":FILE_REGEXP", fileRegexp); + stmt.bindString(":FREE_SPACE_QUERY_URL", freeSpaceQueryURL); + stmt.bindUint64(":REFRESH_INTERVAL", refreshInterval); + stmt.bindUint64(":TARGETED_FREE_SPACE", targetedFreeSpace); + stmt.bindUint64(":SLEEP_TIME", sleepTime); + + stmt.bindString(":USER_COMMENT", comment); + + stmt.bindString(":CREATION_LOG_USER_NAME", admin.username); + stmt.bindString(":CREATION_LOG_HOST_NAME", admin.host); + stmt.bindUint64(":CREATION_LOG_TIME", now); + + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + + stmt.executeNonQuery(); + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// deleteDiskSystem +//------------------------------------------------------------------------------ +void RdbmsCatalogue::deleteDiskSystem(const std::string &name) { + try { + const char *const delete_sql = + "DELETE " + "FROM " + "DISK_SYSTEM " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(delete_sql); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + // The delete statement will effect no rows and will not raise an error if + // either the tape does not exist or if it still has tape files + if(0 == stmt.getNbAffectedRows()) { + if(diskSystemExists(conn, name)) { + throw UserSpecifiedANonEmptyDiskSystemAfterDelete(std::string("Cannot delete disk system ") + name + " for unknown reason"); + } else { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot delete disk system ") + name + " because it does not exist"); + } + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +void RdbmsCatalogue::modifyDiskSystemFileRegexp(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &fileRegexp) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(fileRegexp.empty()) { + throw UserSpecifiedAnEmptyStringFileRegexp("Cannot modify disk system " + "because the new fileRegexp is an empty string"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "FILE_REGEXP = :FILE_REGEXP," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindString(":FILE_REGEXP", fileRegexp); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +void RdbmsCatalogue::modifyDiskSystemFreeSpaceQueryURL(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &freeSpaceQueryURL) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(freeSpaceQueryURL.empty()) { + throw UserSpecifiedAnEmptyStringFreeSpaceQueryURL("Cannot modify disk system " + "because the new freeSpaceQueryURL is an empty string"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "FREE_SPACE_QUERY_URL = :FREE_SPACE_QUERY_URL," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindString(":FREE_SPACE_QUERY_URL", freeSpaceQueryURL); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +void RdbmsCatalogue::modifyDiskSystemRefreshInterval(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t refreshInterval) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(0 == refreshInterval) { + throw UserSpecifiedAZeroRefreshInterval("Cannot modify disk system " + "because the new refresh interval has zero value"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "REFRESH_INTERVAL = :REFRESH_INTERVAL," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindUint64(":REFRESH_INTERVAL", refreshInterval); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +void RdbmsCatalogue::modifyDiskSystemTargetedFreeSpace(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t targetedFreeSpace) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(0 == targetedFreeSpace) { + throw UserSpecifiedAZeroTargetedFreeSpace("Cannot modify disk system " + "because the new targeted free space has zero value"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "TARGETED_FREE_SPACE = :TARGETED_FREE_SPACE," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindUint64(":TARGETED_FREE_SPACE", targetedFreeSpace); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +void RdbmsCatalogue::modifyDiskSystemComment(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &comment) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(comment.empty()) { + throw UserSpecifiedAnEmptyStringComment("Cannot modify disk system " + "because the new comment is an empty string"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "USER_COMMENT = :USER_COMMENT," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindString(":USER_COMMENT", comment); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + +//------------------------------------------------------------------------------ +// modifyDiskSystemSleepTime +//------------------------------------------------------------------------------ +void RdbmsCatalogue::modifyDiskSystemSleepTime(const common::dataStructures::SecurityIdentity& admin, const std::string& name, + const uint64_t sleepTime) { + try { + if(name.empty()) { + throw UserSpecifiedAnEmptyStringDiskSystemName("Cannot modify disk system" + " because the disk system name is an empty string"); + } + if(sleepTime == 0) { + throw UserSpecifiedAZeroSleepTime("Cannot modify disk system " + "because the new sleep time is zero"); + } + + const time_t now = time(nullptr); + const char *const sql = + "UPDATE DISK_SYSTEM SET " + "SLEEP_TIME = :SLEEP_TIME," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_SYSTEM_NAME = :DISK_SYSTEM_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindUint64(":SLEEP_TIME", sleepTime); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_SYSTEM_NAME", name); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw UserSpecifiedANonExistentDiskSystem(std::string("Cannot modify disk system ") + name + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // insertArchiveFile //------------------------------------------------------------------------------ diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp index 4e5a9515b5624061341ec897a6d98e8d14f87445..8de37183d37975c593b9b26a5a86e9daaad5ebdb 100644 --- a/catalogue/RdbmsCatalogue.hpp +++ b/catalogue/RdbmsCatalogue.hpp @@ -561,6 +561,60 @@ public: void deleteActivitiesFairShareWeight(const common::dataStructures::SecurityIdentity &admin, const std::string & diskInstanceName, const std::string & activity) override; std::list<common::dataStructures::ActivitiesFairShareWeights> getActivitiesFairShareWeights() const override; + /** + * Returns all the disk systems within the CTA catalogue. + * + * @return The disk systems. + * requester group. + */ + disk::DiskSystemList getAllDiskSystems() const override; + + /** + * Creates a disk system. + * + * @param admin The administrator. + * @param name The name of the disk system. + * @param fileRegexp The regular expression allowing matching destination URLs + * for this disk system. + * @param freeSpaceQueryURL The query URL that describes a method to query the + * free space from the disk system. + * @param refreshInterval The refresh interval (seconds) defining how long do + * we use a free space value. + * @param targetedFreeSpace The targeted free space (margin) based on the free + * space update latency (inherent to the file system and induced by the refresh + * interval), and the expected external bandwidth from sources external to CTA. + * @param comment Comment. + */ + void createDiskSystem( + const common::dataStructures::SecurityIdentity &admin, + const std::string &name, + const std::string &fileRegexp, + const std::string &freeSpaceQueryURL, + const uint64_t refreshInterval, + const uint64_t targetedFreeSpace, + const uint64_t sleepTime, + const std::string &comment) override; + + /** + * Deletes a disk system. + * + * @param name The name of the disk system. + */ + void deleteDiskSystem(const std::string &name) override; + + void modifyDiskSystemFileRegexp(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &fileRegexp) override; + void modifyDiskSystemFreeSpaceQueryURL(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &freeSpaceQueryURL) override; + void modifyDiskSystemRefreshInterval(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t refreshInterval) override; + void modifyDiskSystemTargetedFreeSpace(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const uint64_t targetedFreeSpace) override; + void modifyDiskSystemComment(const common::dataStructures::SecurityIdentity &admin, + const std::string &name, const std::string &comment) override; + void modifyDiskSystemSleepTime(const common::dataStructures::SecurityIdentity& admin, + const std::string& name, const uint64_t sleepTime) override; + /** * Throws a UserError exception if the specified searchCriteria is not valid @@ -806,6 +860,23 @@ protected: * @return True if the tape exists. */ bool tapeExists(rdbms::Conn &conn, const std::string &vid) const; + + /** + * Returns true if the specified disk system exists. + * + * @param name The name identifier of the disk system. + * @return True if the tape exists. + */ + bool diskSystemExists(const std::string &name) const override; + + /** + * Returns true if the specified disk system exists. + * + * @param conn The database connection. + * @param name The name identifier of the disk system. + * @return True if the disk system exists. + */ + bool diskSystemExists(rdbms::Conn &conn, const std::string &name) const; /** * Returns the list of tapes that meet the specified search criteria. diff --git a/objectstore/OwnerIdentitySerDeser.hpp b/catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.cpp similarity index 56% rename from objectstore/OwnerIdentitySerDeser.hpp rename to catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.cpp index e54d396479b1124cdb4cadb197a1f925554ad62a..8e0ce64760eee2cf19aaa98a0d038208a4a796cc 100644 --- a/objectstore/OwnerIdentitySerDeser.hpp +++ b/catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.cpp @@ -1,6 +1,6 @@ -/** +/* * The CERN Tape Archive (CTA) project - * Copyright (C) 2019 CERN + * Copyright (C) 2015 CERN * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,24 +16,17 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#pragma once +#include "UserSpecifiedANonEmptyDiskSystemAfterDelete.hpp" -#include <string> +namespace cta { +namespace catalogue { -#include "common/dataStructures/OwnerIdentity.hpp" -#include "objectstore/cta.pb.h" +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedANonEmptyDiskSystemAfterDelete::UserSpecifiedANonEmptyDiskSystemAfterDelete(const std::string &context, const bool embedBacktrace): + UserError(context, embedBacktrace) { +} -namespace cta { namespace objectstore { - -struct OwnerIdentitySerDeser: public cta::common::dataStructures::OwnerIdentity { - - void serialize(cta::objectstore::serializers::OwnerIdentity &user) const { - user.set_uid(uid); - user.set_gid(gid); - } - - void deserialize(const cta::objectstore::serializers::OwnerIdentity &user) : - uid(user.uid()), gid(user.gid()) {} -}; - -}} +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.hpp b/catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fd679e7fb25d6d0e857e76cce59ba4e18ecabc35 --- /dev/null +++ b/catalogue/UserSpecifiedANonEmptyDiskSystemAfterDelete.hpp @@ -0,0 +1,45 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User error thrown when a disk system was not deleted for unknown reason. + */ +class UserSpecifiedANonEmptyDiskSystemAfterDelete: public exception::UserError { +public: + + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedANonEmptyDiskSystemAfterDelete(const std::string &context = "", const bool embedBacktrace = true); + +}; // class UserSpecifiedANonEmptyDiskSystemAfterDelete + +} // namespace catalogue +} // namespace cta diff --git a/objectstore/RequesterIdentitySerDeser.hpp b/catalogue/UserSpecifiedANonExistentDiskSystem.cpp similarity index 58% rename from objectstore/RequesterIdentitySerDeser.hpp rename to catalogue/UserSpecifiedANonExistentDiskSystem.cpp index c4ba50f29e1118b235bb1a4915ac0b52e5a66564..f5ad9a9348673ab53a76adebe062dfb80deb56c7 100644 --- a/objectstore/RequesterIdentitySerDeser.hpp +++ b/catalogue/UserSpecifiedANonExistentDiskSystem.cpp @@ -16,24 +16,17 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#pragma once +#include "UserSpecifiedANonExistentDiskSystem.hpp" -#include <string> +namespace cta { +namespace catalogue { -#include "common/dataStructures/RequesterIdentity.hpp" -#include "objectstore/cta.pb.h" +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedANonExistentDiskSystem::UserSpecifiedANonExistentDiskSystem(const std::string &context, const bool embedBacktrace): + UserError(context, embedBacktrace) { +} -namespace cta { namespace objectstore { - -struct RequesterIdentitySerDeser: public cta::common::dataStructures::RequesterIdentity { - - void serialize (cta::objectstore::serializers::RequesterIdentity & user) const { - user.set_name(name); - user.set_group(group); - } - - void deserialize (const cta::objectstore::serializers::RequesterIdentity & user) : - name(user.name()), group(user.group()) {} -}; - -}} +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedANonExistentDiskSystem.hpp b/catalogue/UserSpecifiedANonExistentDiskSystem.hpp new file mode 100644 index 0000000000000000000000000000000000000000..05dd1da503d837c0431cc8175c1c057fd599af61 --- /dev/null +++ b/catalogue/UserSpecifiedANonExistentDiskSystem.hpp @@ -0,0 +1,45 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User error thrown when a disk system specified does not exists. + */ +class UserSpecifiedANonExistentDiskSystem: public exception::UserError { +public: + + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedANonExistentDiskSystem(const std::string &context = "", const bool embedBacktrace = true); + +}; // class UserSpecifiedANonExistentDiskSystem + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAZeroRefreshInterval.cpp b/catalogue/UserSpecifiedAZeroRefreshInterval.cpp new file mode 100644 index 0000000000000000000000000000000000000000..689ee12e2825755f55f32d9e84ff2bd3f0c53a9a --- /dev/null +++ b/catalogue/UserSpecifiedAZeroRefreshInterval.cpp @@ -0,0 +1,39 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "catalogue/UserSpecifiedAZeroRefreshInterval.hpp" + +namespace cta { +namespace catalogue { + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedAZeroRefreshInterval::UserSpecifiedAZeroRefreshInterval(const std::string &context, const bool embedBacktrace): + cta::exception::UserError(context, embedBacktrace) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +UserSpecifiedAZeroRefreshInterval::~UserSpecifiedAZeroRefreshInterval() { +} + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAZeroRefreshInterval.hpp b/catalogue/UserSpecifiedAZeroRefreshInterval.hpp new file mode 100644 index 0000000000000000000000000000000000000000..08680ce1602466c68001d30897b0c55f4175299e --- /dev/null +++ b/catalogue/UserSpecifiedAZeroRefreshInterval.hpp @@ -0,0 +1,48 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User specified a zero refresh interval when this is not permitted. + */ +class UserSpecifiedAZeroRefreshInterval: public exception::UserError { +public: + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedAZeroRefreshInterval(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Destructor. + */ + ~UserSpecifiedAZeroRefreshInterval() override; +}; // class UserSpecifiedAZeroRefreshInterval + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAZeroTargetedFreeSpace.cpp b/catalogue/UserSpecifiedAZeroTargetedFreeSpace.cpp new file mode 100644 index 0000000000000000000000000000000000000000..550de7ef660e1e797642c98d66904097f281e2e2 --- /dev/null +++ b/catalogue/UserSpecifiedAZeroTargetedFreeSpace.cpp @@ -0,0 +1,39 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp" + +namespace cta { +namespace catalogue { + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedAZeroTargetedFreeSpace::UserSpecifiedAZeroTargetedFreeSpace(const std::string &context, const bool embedBacktrace): + cta::exception::UserError(context, embedBacktrace) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +UserSpecifiedAZeroTargetedFreeSpace::~UserSpecifiedAZeroTargetedFreeSpace() { +} + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp b/catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f5d8cc3444973852912d51ced9a67a0ca6cc0db1 --- /dev/null +++ b/catalogue/UserSpecifiedAZeroTargetedFreeSpace.hpp @@ -0,0 +1,48 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User specified a zero value for a targeted free space when this is not permitted. + */ +class UserSpecifiedAZeroTargetedFreeSpace: public exception::UserError { +public: + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedAZeroTargetedFreeSpace(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Destructor. + */ + ~UserSpecifiedAZeroTargetedFreeSpace() override; +}; // class UserSpecifiedAZeroTargetedFreeSpace + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.cpp b/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c814f346755b1f9bab77737249055e376b4dc4a2 --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.cpp @@ -0,0 +1,39 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp" + +namespace cta { +namespace catalogue { + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringDiskSystemName::UserSpecifiedAnEmptyStringDiskSystemName(const std::string &context, const bool embedBacktrace): + cta::exception::UserError(context, embedBacktrace) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringDiskSystemName::~UserSpecifiedAnEmptyStringDiskSystemName() { +} + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp b/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp new file mode 100644 index 0000000000000000000000000000000000000000..efbc2d811c8983f01cc8e853351243e225c93c7e --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringDiskSystemName.hpp @@ -0,0 +1,48 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User specified an empty string for a disk system name when this is not permitted. + */ +class UserSpecifiedAnEmptyStringDiskSystemName: public exception::UserError { +public: + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedAnEmptyStringDiskSystemName(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Destructor. + */ + ~UserSpecifiedAnEmptyStringDiskSystemName() override; +}; // class UserSpecifiedAnEmptyStringDiskSystemName + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringFileRegexp.cpp b/catalogue/UserSpecifiedAnEmptyStringFileRegexp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..331d34c8c7d01971e8ce518cbb93e2600a414079 --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringFileRegexp.cpp @@ -0,0 +1,39 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp" + +namespace cta { +namespace catalogue { + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringFileRegexp::UserSpecifiedAnEmptyStringFileRegexp(const std::string &context, const bool embedBacktrace): + cta::exception::UserError(context, embedBacktrace) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringFileRegexp::~UserSpecifiedAnEmptyStringFileRegexp() { +} + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp b/catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c1899e48cc2c5475644d112621adb25eef6e986d --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringFileRegexp.hpp @@ -0,0 +1,48 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User specified an empty string for a file regexp when this is not permitted. + */ +class UserSpecifiedAnEmptyStringFileRegexp: public exception::UserError { +public: + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedAnEmptyStringFileRegexp(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Destructor. + */ + ~UserSpecifiedAnEmptyStringFileRegexp() override; +}; // class UserSpecifiedAnEmptyStringFileRegexp + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.cpp b/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d1eb07ae92e2265a5acec1adb7287437101afa0 --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.cpp @@ -0,0 +1,39 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp" + +namespace cta { +namespace catalogue { + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringFreeSpaceQueryURL::UserSpecifiedAnEmptyStringFreeSpaceQueryURL(const std::string &context, const bool embedBacktrace): + cta::exception::UserError(context, embedBacktrace) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +UserSpecifiedAnEmptyStringFreeSpaceQueryURL::~UserSpecifiedAnEmptyStringFreeSpaceQueryURL() { +} + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp b/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3fb8d674ac6c0266b103e36548789f43ba33c15c --- /dev/null +++ b/catalogue/UserSpecifiedAnEmptyStringFreeSpaceQueryURL.hpp @@ -0,0 +1,48 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/UserError.hpp" + +namespace cta { +namespace catalogue { + +/** + * User specified an empty string for a free space query URL when this is not permitted. + */ +class UserSpecifiedAnEmptyStringFreeSpaceQueryURL: public exception::UserError { +public: + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + UserSpecifiedAnEmptyStringFreeSpaceQueryURL(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Destructor. + */ + ~UserSpecifiedAnEmptyStringFreeSpaceQueryURL() override; +}; // class UserSpecifiedAnEmptyStringFreeSpaceQueryURL + +} // namespace catalogue +} // namespace cta diff --git a/catalogue/common_catalogue_schema.sql b/catalogue/common_catalogue_schema.sql index d6614edcf0b6648f865912840b1aca5a1ea4262f..b848cea07c2f03e3f5cf244987dda0e5b8b615f0 100644 --- a/catalogue/common_catalogue_schema.sql +++ b/catalogue/common_catalogue_schema.sql @@ -13,6 +13,22 @@ CREATE TABLE ADMIN_USER( LAST_UPDATE_TIME NUMERIC(20, 0) CONSTRAINT ADMIN_USER_LUT_NN NOT NULL, CONSTRAINT ADMIN_USER_PK PRIMARY KEY(ADMIN_USER_NAME) ); +CREATE TABLE DISK_SYSTEM( + DISK_SYSTEM_NAME VARCHAR(100) CONSTRAINT DISK_SYSTEM_DSNM_NN NOT NULL, + FILE_REGEXP VARCHAR(100) CONSTRAINT DISK_SYSTEM_FR_NN NOT NULL, + FREE_SPACE_QUERY_URL VARCHAR(1000) CONSTRAINT DISK_SYSTEM_FSQU_NN NOT NULL, + REFRESH_INTERVAL NUMERIC(20, 0) CONSTRAINT DISK_SYSTEM_RI_NN NOT NULL, + TARGETED_FREE_SPACE NUMERIC(20, 0) CONSTRAINT DISK_SYSTEM_TFS_NN NOT NULL, + SLEEP_TIME NUMERIC(20, 0) CONSTRAINT DISK_SYSTEM_ST_NN NOT NULL, + USER_COMMENT VARCHAR(1000) CONSTRAINT DISK_SYSTEM_UC_NN NOT NULL, + CREATION_LOG_USER_NAME VARCHAR(100) CONSTRAINT DISK_SYSTEM_CLUN_NN NOT NULL, + CREATION_LOG_HOST_NAME VARCHAR(100) CONSTRAINT DISK_SYSTEM_CLHN_NN NOT NULL, + CREATION_LOG_TIME NUMERIC(20, 0) CONSTRAINT DISK_SYSTEM_CLT_NN NOT NULL, + LAST_UPDATE_USER_NAME VARCHAR(100) CONSTRAINT DISK_SYSTEM_LUUN_NN NOT NULL, + LAST_UPDATE_HOST_NAME VARCHAR(100) CONSTRAINT DISK_SYSTEM_LUHN_NN NOT NULL, + LAST_UPDATE_TIME NUMERIC(20, 0) CONSTRAINT DISK_SYSTEM_LUT_NN NOT NULL, + CONSTRAINT NAME_PK PRIMARY KEY(DISK_SYSTEM_NAME) +); CREATE TABLE STORAGE_CLASS( STORAGE_CLASS_ID NUMERIC(20, 0) CONSTRAINT STORAGE_CLASS_SCI_NN NOT NULL, DISK_INSTANCE_NAME VARCHAR(100) CONSTRAINT STORAGE_CLASS_DIN_NN NOT NULL, diff --git a/cmake/UseRPMTools.cmake b/cmake/UseRPMTools.cmake index b22729a03ec95c4be79792155322897a62bff867..2ab6553d4fe00a0df75052d1f4bf826b6a26007b 100644 --- a/cmake/UseRPMTools.cmake +++ b/cmake/UseRPMTools.cmake @@ -27,7 +27,7 @@ IF (UNIX) DOC "The RPM builder tool") IF (RPMTools_RPMBUILD_EXECUTABLE) - MESSAGE(STATUS "Looking for RPMTools... - found rpmuild is ${RPMTools_RPMBUILD_EXECUTABLE}") + MESSAGE(STATUS "Looking for RPMTools... - found rpmbuild in ${RPMTools_RPMBUILD_EXECUTABLE}") SET(RPMTools_RPMBUILD_FOUND "YES") GET_FILENAME_COMPONENT(RPMTools_BINARY_DIRS ${RPMTools_RPMBUILD_EXECUTABLE} PATH) ELSE (RPMTools_RPMBUILD_EXECUTABLE) diff --git a/cmdline/CtaAdminCmd.cpp b/cmdline/CtaAdminCmd.cpp index a65c5bfa02c10c206ee9aaad4191c936684bdedc..5950259823c26700a2e8f47cadb48644416da0ba 100644 --- a/cmdline/CtaAdminCmd.cpp +++ b/cmdline/CtaAdminCmd.cpp @@ -91,6 +91,7 @@ void IStreamBuffer<cta::xrd::Data>::DataCallback(cta::xrd::Data record) const case Data::kSclsItem: std::cout << Log::DumpProtobuf(&record.scls_item()); break; case Data::kTalsItem: std::cout << Log::DumpProtobuf(&record.tals_item()); break; case Data::kTplsItem: std::cout << Log::DumpProtobuf(&record.tpls_item()); break; + case Data::kDslsItem: std::cout << Log::DumpProtobuf(&record.dsls_item()); break; default: throw std::runtime_error("Received invalid stream data from CTA Frontend."); } @@ -117,6 +118,7 @@ void IStreamBuffer<cta::xrd::Data>::DataCallback(cta::xrd::Data record) const case Data::kSclsItem: formattedText.print(record.scls_item()); break; case Data::kTalsItem: formattedText.print(record.tals_item()); break; case Data::kTplsItem: formattedText.print(record.tpls_item()); break; + case Data::kDslsItem: formattedText.print(record.dsls_item()); break; default: throw std::runtime_error("Received invalid stream data from CTA Frontend."); } @@ -270,6 +272,7 @@ void CtaAdminCmd::send() const case HeaderType::STORAGECLASS_LS: formattedText.printStorageClassLsHeader(); break; case HeaderType::TAPE_LS: formattedText.printTapeLsHeader(); break; case HeaderType::TAPEPOOL_LS: formattedText.printTapePoolLsHeader(); break; + case HeaderType::DISKSYSTEM_LS: formattedText.printDiskSystemLsHeader(); break; case HeaderType::NONE: default: break; } diff --git a/cmdline/CtaAdminCmdParse.hpp b/cmdline/CtaAdminCmdParse.hpp index 23e8316ec7410b14b6223f33f96a9cdc90845b4e..fa080f80849714aeff28ca1d95b1d1ec7a57ce17 100644 --- a/cmdline/CtaAdminCmdParse.hpp +++ b/cmdline/CtaAdminCmdParse.hpp @@ -211,6 +211,8 @@ const cmdLookup_t cmdLookup = { { "ta", AdminCmd::CMD_TAPE }, { "tapepool", AdminCmd::CMD_TAPEPOOL }, { "tp", AdminCmd::CMD_TAPEPOOL }, + { "disksystem", AdminCmd::CMD_DISKSYSTEM }, + { "ds", AdminCmd::CMD_DISKSYSTEM } }; @@ -279,6 +281,9 @@ const std::map<std::string, OptionUInt64::Key> uint64Options = { { "--partialtapesnumber", OptionUInt64::PARTIAL_TAPES_NUMBER }, { "--retrievepriority", OptionUInt64::RETRIEVE_PRIORITY }, { "--size", OptionUInt64::FILE_SIZE }, + { "--refreshinterval", OptionUInt64::REFRESH_INTERVAL }, + { "--targetedfreespace", OptionUInt64::TARGETED_FREE_SPACE }, + { "--sleeptime", OptionUInt64::SLEEP_TIME }, { "--uid", OptionUInt64::OWNER_UID } }; @@ -308,7 +313,10 @@ const std::map<std::string, OptionString::Key> strOptions = { { "--username", OptionString::USERNAME }, { "--vendor", OptionString::VENDOR }, { "--vid", OptionString::VID }, - { "--vo", OptionString::VO } + { "--vo", OptionString::VO }, + { "--disksystem", OptionString::DISK_SYSTEM }, + { "--fileregexp", OptionString::FILE_REGEXP }, + { "--freespacequeryurl", OptionString::FREE_SPACE_QUERY_URL } }; @@ -360,6 +368,7 @@ const std::map<AdminCmd::Cmd, CmdHelp> cmdHelp = { { AdminCmd::CMD_STORAGECLASS, { "storageclass", "sc", { "add", "ch", "rm", "ls" } }}, { AdminCmd::CMD_TAPE, { "tape", "ta", { "add", "ch", "rm", "reclaim", "ls", "label" } }}, { AdminCmd::CMD_TAPEPOOL, { "tapepool", "tp", { "add", "ch", "rm", "ls" } }}, + { AdminCmd::CMD_DISKSYSTEM, { "disksystem", "ds", { "add", "ch", "rm", "ls" } }}, }; @@ -436,7 +445,12 @@ const Option opt_full { Option::OPT_BOOL, "--full", const Option opt_readonly { Option::OPT_BOOL, "--readonly", "-r", " <\"true\" or \"false\">" }; const Option opt_disabled_tape { Option::OPT_FLAG, "--disabledtape", "-d", ""}; - +const Option opt_disksystem { Option::OPT_STR, "--disksystem", "-n", " <disk_system_name>" }; +const Option opt_file_regexp { Option::OPT_STR, "--fileregexp", "-r", " <file_regexp>" }; +const Option opt_free_space_query_url { Option::OPT_STR, "--freespacequeryurl", "-u", " <free_space_query_url>" }; +const Option opt_refresh_interval { Option::OPT_UINT, "--refreshinterval", "-i", " <refresh_intreval>" }; +const Option opt_targeted_free_space { Option::OPT_UINT, "--targetedfreespace", "-f", " <targeted_free_space>" }; +const Option opt_sleep_time { Option::OPT_UINT, "--sleeptime", "-s", " <sleep time in s>" }; /*! * Map valid options to commands @@ -547,6 +561,15 @@ const std::map<cmd_key_t, cmd_val_t> cmdOptions = { opt_supply.optional(), opt_comment.optional() }}, {{ AdminCmd::CMD_TAPEPOOL, AdminCmd::SUBCMD_RM }, { opt_tapepool_alias }}, {{ AdminCmd::CMD_TAPEPOOL, AdminCmd::SUBCMD_LS }, { }}, + /*----------------------------------------------------------------------------------------------------*/ + {{ AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_ADD }, + { opt_disksystem, opt_file_regexp, opt_free_space_query_url, opt_refresh_interval, opt_targeted_free_space, opt_sleep_time, + opt_comment }}, + {{ AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_CH }, + { opt_disksystem, opt_file_regexp.optional(), opt_free_space_query_url.optional(), opt_refresh_interval.optional(), + opt_targeted_free_space.optional(), opt_sleep_time.optional(), opt_comment.optional() }}, + {{ AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_RM }, { opt_disksystem }}, + {{ AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_LS }, { }}, }; diff --git a/cmdline/CtaAdminTextFormatter.cpp b/cmdline/CtaAdminTextFormatter.cpp index 7a0b23a74737969c2fbcb178f00340afe52c5198..810c0c8a3de5362633dbf446d3cccc2676556a78 100644 --- a/cmdline/CtaAdminTextFormatter.cpp +++ b/cmdline/CtaAdminTextFormatter.cpp @@ -894,4 +894,42 @@ void TextFormatter::print(const TapePoolLsItem &tpls_item) ); } +void TextFormatter::printDiskSystemLsHeader() { + push_back("HEADER"); + push_back( + "name", + "regexp", + "url", + "refresh", + "space", + "sleep", + "c.user", + "c.host", + "c.time", + "m.user", + "m.host", + "m.time", + "comment" + ); +} + +void TextFormatter::print(const DiskSystemLsItem &dsls_item) +{ + push_back( + dsls_item.name(), + dsls_item.file_regexp(), + dsls_item.free_space_query_url(), + dsls_item.refresh_interval(), + dsls_item.targeted_free_space(), + dsls_item.sleep_time(), + dsls_item.creation_log().username(), + dsls_item.creation_log().host(), + timeToStr(dsls_item.creation_log().time()), + dsls_item.last_modification_log().username(), + dsls_item.last_modification_log().host(), + timeToStr(dsls_item.last_modification_log().time()), + dsls_item.comment() + ); +} + }} diff --git a/cmdline/CtaAdminTextFormatter.hpp b/cmdline/CtaAdminTextFormatter.hpp index 533e534f5d414e4a322175f45890fbf57a20d1a0..f8acc3d8ad78577c5bd6029781da08d68d766397 100644 --- a/cmdline/CtaAdminTextFormatter.hpp +++ b/cmdline/CtaAdminTextFormatter.hpp @@ -64,6 +64,7 @@ public: void printStorageClassLsHeader(); void printTapeLsHeader(); void printTapePoolLsHeader(); + void printDiskSystemLsHeader(); // Output records void print(const AdminLsItem &adls_item); @@ -86,6 +87,8 @@ public: void print(const StorageClassLsItem &scls_item); void print(const TapeLsItem &tals_item); void print(const TapePoolLsItem &tpls_item); + void print(const DiskSystemLsItem &dsls_item); + private: //! Add a line to the buffer diff --git a/common/dataStructures/ArchiveFile.hpp b/common/dataStructures/ArchiveFile.hpp index 73cb3c5eadac1e1d3b8e6cb605d5a93595e2a9af..76e56c274a41ff38228ab73bda71ce1f91e4eb1c 100644 --- a/common/dataStructures/ArchiveFile.hpp +++ b/common/dataStructures/ArchiveFile.hpp @@ -57,7 +57,7 @@ struct ArchiveFile { std::string storageClass; DiskFileInfo diskFileInfo; /** - * This map represents the non-necessarily-exhaustive set of tape copies + * This list represents the non-necessarily-exhaustive set of tape copies * to be listed by the operator. For example, if the listing requested is * for a single tape, the map will contain only one element. */ diff --git a/common/dataStructures/QueueAndMountSummary.hpp b/common/dataStructures/QueueAndMountSummary.hpp index 685e4ba5cd8673bf323d53767befa45407212ca8..b6ef864047b88ba46c1eddae0fb52f4aa7eee6ec 100644 --- a/common/dataStructures/QueueAndMountSummary.hpp +++ b/common/dataStructures/QueueAndMountSummary.hpp @@ -52,6 +52,12 @@ struct QueueAndMountSummary { uint64_t emptyTapes=0; uint64_t disabledTapes=0; uint64_t writableTapes=0; + struct SleepForSpaceInfo { + time_t startTime; + std::string diskSystemName; + time_t sleepTime; + }; + optional<SleepForSpaceInfo> sleepForSpaceInfo; static QueueAndMountSummary &getOrCreateEntry(std::list<QueueAndMountSummary> &summaryList, MountType mountType, const std::string &tapePool, const std::string &vid, diff --git a/common/utils/Regex.cpp b/common/utils/Regex.cpp index b3fa149fc33721ad65b14dd1f36bfbafdfdf14d1..60a91ce24e797c9e28c5b71f79978db0d3a4a7b3 100644 --- a/common/utils/Regex.cpp +++ b/common/utils/Regex.cpp @@ -27,10 +27,10 @@ namespace cta { namespace utils { -Regex::Regex(const char * re_str) : m_set(false) { - if (int rc = ::regcomp(&m_re, re_str, REG_EXTENDED)) { +Regex::Regex(const std::string & re_str) : m_reStr(re_str), m_set(false) { + if (int rc = ::regcomp(&m_re, m_reStr.c_str(), REG_EXTENDED)) { std::string error("Could not compile regular expression: \""); - error += re_str; + error += m_reStr; error += "\""; char re_err[1024]; if (::regerror(rc, &m_re, re_err, sizeof (re_err))) { @@ -42,12 +42,16 @@ Regex::Regex(const char * re_str) : m_set(false) { m_set = true; } +Regex::Regex(const Regex& o) { + Regex(o.m_reStr); +} + Regex::~Regex() { if (m_set) ::regfree(&m_re); } -std::vector<std::string> Regex::exec(const std::string &s) { +std::vector<std::string> Regex::exec(const std::string &s) const { regmatch_t matches[100]; if (REG_NOMATCH != ::regexec(&m_re, s.c_str(), 100, matches, 0)) { std::vector<std::string> ret; diff --git a/common/utils/Regex.hpp b/common/utils/Regex.hpp index 834a54d8f8ef10b8501b2cbceb8a67cad4be64e2..5b5bf259d72a88d159f9c253345a7a8fdff418aa 100644 --- a/common/utils/Regex.hpp +++ b/common/utils/Regex.hpp @@ -29,13 +29,14 @@ namespace cta { namespace utils { class Regex { public: - Regex(const char * re_str); + Regex(const std::string & re_str); + Regex(const Regex & o); virtual ~Regex(); /*! * Return a list of matching substrings */ - std::vector<std::string> exec(const std::string &s); + std::vector<std::string> exec(const std::string &s) const; /*! * Return true if there is at least one matching substring @@ -45,6 +46,8 @@ class Regex { } private: + // We keep around the string from which the RE was compiled to allow copying. + std::string m_reStr; regex_t m_re; bool m_set; }; diff --git a/continuousintegration/buildtree_runner/recreate_buildtree_running_environment.sh b/continuousintegration/buildtree_runner/recreate_buildtree_running_environment.sh index ed722256e5a5305d473d6b0c5b2ceafffb9122e5..4cdff33c6df793574097c1678d0dd7db851bb647 100755 --- a/continuousintegration/buildtree_runner/recreate_buildtree_running_environment.sh +++ b/continuousintegration/buildtree_runner/recreate_buildtree_running_environment.sh @@ -73,3 +73,9 @@ mkdir -pv /shared/stg sudo kubectl delete pv stg || true sudo kubectl create -f ./stg_PV.yaml sudo kubectl get persistentvolumes -l type=stg + +echo Generating the share host path +rm -rf /shared/cta +mkdir -pv /shared/cta/catdb +touch /shared/cta/catdb/catdb +chmod 1777 /shared/cta diff --git a/continuousintegration/buildtree_runner/vmBootstrap/bootstrapCTA.sh b/continuousintegration/buildtree_runner/vmBootstrap/bootstrapCTA.sh index 7ea4bf7496b0faf839c16c8a5ffaf8f0656a3742..219ed85fe634c48079ff3d54a9d11eaa7ffad4eb 100755 --- a/continuousintegration/buildtree_runner/vmBootstrap/bootstrapCTA.sh +++ b/continuousintegration/buildtree_runner/vmBootstrap/bootstrapCTA.sh @@ -14,13 +14,21 @@ chmod +x ~/CTA/.git/hooks/post-checkout cp ~/CTA/.git/hooks/post-checkout ~/CTA/.git/hooks/post-merge echo Creating source rpm +rm -rf ~/CTA-build-srpm mkdir -p ~/CTA-build-srpm (cd ~/CTA-build-srpm && cmake -DPackageOnly:Bool=true ../CTA; make cta_srpm) echo Installing repos +<<<<<<< HEAD +for r in `ls -1 ../../docker/ctafrontend/cc7/etc/yum.repos.d/*.repo`; do + yum-config-manager --add-repo=$r +done +sudo yum-config-manager --add-repo=ceph-internal.repo +======= for r in `ls ../../docker/ctafrontend/cc7/etc/yum.repos.d/*.repo` ceph-internal.repo; do sudo yum-config-manager --add-repo=$r done +>>>>>>> origin/master sudo yum install -y yum-plugin-priorities echo Adding versionlock for xrootd: diff --git a/continuousintegration/orchestration/tests/repack_systemtest.sh b/continuousintegration/orchestration/tests/repack_systemtest.sh index b206f30c7617cbafa1c47ecc290df22e90896dff..dd6d8c01cdd3464cdc915c18c160ad15493da5e3 100755 --- a/continuousintegration/orchestration/tests/repack_systemtest.sh +++ b/continuousintegration/orchestration/tests/repack_systemtest.sh @@ -112,10 +112,38 @@ admin_cta repack rm --vid ${VID_TO_REPACK} echo "Marking the tape ${VID_TO_REPACK} as full before Repacking it" admin_cta tape ch --vid ${VID_TO_REPACK} --full true +echo "Backpressure test: setting too high free space requirements" +# This should be idempotent as we will be called several times +if [[ $( admin_cta --json ds ls | jq '.[] | select(.name=="repackBuffer") | .name') != '"repackBuffer"' ]]; then + admin_cta ds add -n repackBuffer -r "root://${EOSINSTANCE}/${REPACK_BUFFER_BASEDIR}" -u "eos://${EOSINSTANCE}" -i 5 -f 111222333444555 -s 60 -m toto +else + echo "Disk system repackBuffer alread defined. Ensuring too high free space requirements." + admin_cta ds ch -n repackBuffer -f 111222333444555 +fi +admin_cta ds ls + echo "Launching repack request for VID ${VID_TO_REPACK}, bufferURL = ${FULL_REPACK_BUFFER_URL}" admin_cta repack add --vid ${VID_TO_REPACK} ${REPACK_OPTION} --bufferurl ${FULL_REPACK_BUFFER_URL} ${DISABLED_TAPE_FLAG} +echo "Backpressure test: waiting to see a report of sleeping retrieve queue." +SECONDS_PASSED=0 +while test 0 = `admin_cta --json sq | jq -r ".[] | select(.vid == \"${VID_TO_REPACK}\" and .sleepingForSpace == true) | .vid" | wc -l`; do + echo "Waiting for retrieve queue for tape ${VID_TO_REPACK} to be sleepting: Seconds passed = $SECONDS_PASSED" + sleep 1 + let SECONDS_PASSED=SECONDS_PASSED+1 + + if test ${SECONDS_PASSED} == ${WAIT_FOR_REPACK_TIMEOUT}; then + echo "Timed out after ${WAIT_FOR_REPACK_TIMEOUT} seconds waiting for tape ${VID_TO_REPACK} to be repacked" + exit 1 + fi +done + +echo "Turning free space requirement to one byte (zero is not allowed)." +admin_cta ds ch -n repackBuffer -f 1 +admin_cta ds ls + +echo "Now waiting for repack to proceed." SECONDS_PASSED=0 while test 0 = `admin_cta --json repack ls --vid ${VID_TO_REPACK} | jq -r '.[0] | select(.status == "Complete" or .status == "Failed")' | wc -l`; do echo "Waiting for repack request on tape ${VID_TO_REPACK} to be complete: Seconds passed = $SECONDS_PASSED" @@ -140,4 +168,4 @@ echo "Repack request on VID ${VID_TO_REPACK} succeeded." exec /root/repack_generate_report.sh -v ${VID_TO_REPACK} -r ${REPORT_DIRECTORY} ${ADD_COPIES_ONLY} & wait $! -exit 0 \ No newline at end of file +exit 0 diff --git a/cta.spec.in b/cta.spec.in index de99a3eba8c738bed0e638bc03c704ade6c00732..922c8fbf1a08f17fb445b110200513ce555d553d 100644 --- a/cta.spec.in +++ b/cta.spec.in @@ -37,10 +37,10 @@ BuildRequires: cmake >= 2.6 redhat-rpm-config # The CTA client is the only component of CTA that can be compiled on both SLC6 # and C77, therefore only the packages it depends on are required for SLC6 %define radosVersion 2:12.2.2 -BuildRequires: xrootd-client-devel >= 1:4.8.2 -BuildRequires: xrootd-devel >= 1:4.8.2 -BuildRequires: xrootd-server-devel >= 1:4.8.2 -BuildRequires: xrootd-private-devel >= 1:4.8.2 +BuildRequires: xrootd-client-devel >= 1:4.10.0 +BuildRequires: xrootd-devel >= 1:4.10.0 +BuildRequires: xrootd-server-devel >= 1:4.10.0 +BuildRequires: xrootd-private-devel >= 1:4.10.0 BuildRequires: librados-devel = %{radosVersion}, libradosstriper-devel = %{radosVersion}, BuildRequires: protobuf3-compiler >= 3.3.1 protobuf3-devel >= 3.3.1 BuildRequires: gmock-devel >= 1.5.0 gtest-devel >= 1.5.0 @@ -110,6 +110,7 @@ Group: Application/CTA Requires: logrotate Requires: cta-common = %{version}-%{release} Requires: cta-lib = %{version}-%{release} +Requires: eos-client %description -n cta-taped CERN Tape Archive: The tape server daemon diff --git a/disk/CMakeLists.txt b/disk/CMakeLists.txt index 7bb142594d7018e4ce92305dc699c3a8076e46a9..b29af87d1f611606f05b00b8a293c568274fb5f5 100644 --- a/disk/CMakeLists.txt +++ b/disk/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(ctadisk SHARED EOSReporter.cpp DiskFile.cpp RadosStriperPool.cpp + DiskSystem.cpp ) target_link_libraries (ctadisk XrdCl cryptopp radosstriper) @@ -42,4 +43,4 @@ set_property(TARGET ctadiskunittests PROPERTY SOVERSION "${CTA_SOVERSION}") set_property(TARGET ctadiskunittests PROPERTY VERSION "${CTA_LIBVERSION}") install(TARGETS ctadisk DESTINATION usr/${CMAKE_INSTALL_LIBDIR}) -install(TARGETS ctadiskunittests DESTINATION usr/${CMAKE_INSTALL_LIBDIR}) \ No newline at end of file +install(TARGETS ctadiskunittests DESTINATION usr/${CMAKE_INSTALL_LIBDIR}) diff --git a/disk/DiskSystem.cpp b/disk/DiskSystem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6453b4d9e9889b74ce7182908562f6988a28eacc --- /dev/null +++ b/disk/DiskSystem.cpp @@ -0,0 +1,148 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "DiskSystem.hpp" + +#include <algorithm> +#include "common/exception/Exception.hpp" +#include "common/threading/SubProcess.hpp" +#include "common/exception/Errnum.hpp" +#include "common/utils/utils.hpp" + +namespace cta { +namespace disk { + +//------------------------------------------------------------------------------ +// DiskSystemList::at() +//------------------------------------------------------------------------------ +const DiskSystem& DiskSystemList::at(const std::string& name) const { + auto dsi = std::find_if(begin(), end(), [&](const DiskSystem& ds){ return ds.name == name;}); + if (dsi != end()) return *dsi; + throw std::out_of_range("In DiskSystemList::at(): name not found."); +} + +//------------------------------------------------------------------------------ +// DiskSystemList::getFSNAme() +//------------------------------------------------------------------------------ +std::string DiskSystemList::getDSNAme(const std::string& fileURL) const { + // First if the regexes have not been created yet, do so. + if (m_pointersAndRegexes.empty() && size()) { + for (const auto &ds: *this) { + m_pointersAndRegexes.emplace_back(ds, ds.fileRegexp); + } + } + // Try and find the fileURL + auto pri = std::find_if(m_pointersAndRegexes.begin(), m_pointersAndRegexes.end(), + [&](const PointerAndRegex & pr){ return !pr.regex.exec(fileURL).empty(); }); + if (pri != m_pointersAndRegexes.end()) { + // We found a match. Let's move the pointer and regex to the front so next file will be faster (most likely). + if (pri != m_pointersAndRegexes.begin()) + m_pointersAndRegexes.splice(m_pointersAndRegexes.begin(), m_pointersAndRegexes, pri); + return pri->ds.name; + } + throw std::out_of_range("In DiskSystemList::getFSNAme(): not match for fileURL"); +} + +//------------------------------------------------------------------------------ +// DiskSystemFreeSpaceList::fetchFileSystemFreeSpace() +//------------------------------------------------------------------------------ +void DiskSystemFreeSpaceList::fetchDiskSystemFreeSpace(const std::set<std::string>& diskSystems, log::LogContext & lc) { + // The real deal: go fetch the file system's free space. + cta::utils::Regex eosDiskSystem("^eos://(.*)$"); + // For testing purposes + cta::utils::Regex constantFreeSpaceDiskSystem("^constantFreeSpace:(.*)"); + for (auto const & ds: diskSystems) { + uint64_t freeSpace; + std::vector<std::string> regexResult; + regexResult = eosDiskSystem.exec(m_systemList.at(ds).freeSpaceQueryURL); + if (regexResult.size()) { + freeSpace = fetchEosFreeSpace(regexResult.at(1), lc); + goto found; + } + regexResult = constantFreeSpaceDiskSystem.exec(m_systemList.at(ds).freeSpaceQueryURL); + if (regexResult.size()) { + freeSpace = fetchConstantFreeSpace(regexResult.at(1), lc); + goto found; + } + throw exception::Exception("In DiskSystemFreeSpaceList::fetchDiskSystemFreeSpace(): could not interpret free space query URL."); + found: + DiskSystemFreeSpace & entry = operator [](ds); + entry.freeSpace = freeSpace; + entry.fetchTime = ::time(nullptr); + entry.targetedFreeSpace = m_systemList.at(ds).targetedFreeSpace; + } +} + +//------------------------------------------------------------------------------ +// DiskSystemFreeSpaceList::fetchFileSystemFreeSpace() +//------------------------------------------------------------------------------ +uint64_t DiskSystemFreeSpaceList::fetchEosFreeSpace(const std::string& instanceAddress, log::LogContext & lc) { + threading::SubProcess sp("/usr/bin/eos", {"/usr/bin/eos", std::string("root://")+instanceAddress, "space", "ls", "-m"}); + sp.wait(); + try { + exception::Errnum::throwOnNonZero(sp.exitValue(), + std::string("In DiskSystemFreeSpaceList::fetchEosFreeSpace(), failed to call \"eos root://") + + instanceAddress + " space ls -m\""); + } catch (exception::Exception & ex) { + ex.getMessage() << " instanceAddress: " << instanceAddress << " stderr: " << sp.stderr(); + throw; + } + if (sp.wasKilled()) { + exception::Exception ex("In DiskSystemFreeSpaceList::fetchEosFreeSpace(): eos space ls -m killed by signal: "); + ex.getMessage() << utils::toString(sp.killSignal()); + throw ex; + } + // Look for the result line for default space. + std::istringstream spStdoutIss(sp.stdout()); + std::string defaultSpaceLine; + utils::Regex defaultSpaceRe("^.*name=default .*$"); + do { + std::string spStdoutLine; + std::getline(spStdoutIss, spStdoutLine); + auto res = defaultSpaceRe.exec(spStdoutLine); + if (res.size()) { + defaultSpaceLine = res.at(0); + goto defaultFound; + } + } while (!spStdoutIss.eof()); + throw exception::Exception("In DiskSystemFreeSpaceList::fetchEosFreeSpace(): could not find line for default space."); + +defaultFound: + // Look for the parameters in the result line. + utils::Regex rwSpaceRegex("sum.stat.statfs.capacity\\?configstatus@rw=([0-9]+) "); + auto rwSpaceRes = rwSpaceRegex.exec(defaultSpaceLine); + if (rwSpaceRes.empty()) + throw exception::Exception( + "In DiskSystemFreeSpaceList::fetchEosFreeSpace(): failed to parse parameter sum.stat.statfs.capacity?configstatus@rw."); + utils::Regex usedSpaceRegex("sum.stat.statfs.usedbytes=([0-9]+) "); + auto usedSpaceRes = usedSpaceRegex.exec(sp.stdout()); + if (usedSpaceRes.empty()) + throw exception::Exception("In DiskSystemFreeSpaceList::fetchEosFreeSpace(): failed to parse parameter sum.stat.statfs.usedbytes."); + return utils::toUint64(rwSpaceRes.at(1)) - utils::toUint64(usedSpaceRes.at(1)); +} + +//------------------------------------------------------------------------------ +// DiskSystemFreeSpaceList::fetchFileSystemFreeSpace() +//------------------------------------------------------------------------------ +uint64_t DiskSystemFreeSpaceList::fetchConstantFreeSpace(const std::string& instanceAddress, log::LogContext & lc) { + return utils::toUint64(instanceAddress); +} + + + +}} // namespace cta::disk \ No newline at end of file diff --git a/disk/DiskSystem.hpp b/disk/DiskSystem.hpp new file mode 100644 index 0000000000000000000000000000000000000000..72afa78992ae715fa33c9b83b3e39d85f7d5678c --- /dev/null +++ b/disk/DiskSystem.hpp @@ -0,0 +1,91 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/utils/Regex.hpp" +#include "common/dataStructures/EntryLog.hpp" +#include "common/log/LogContext.hpp" +#include <string> +#include <list> +#include <set> + +namespace cta { namespace disk { + +/** + * Description of a disk system as defined by operators. + * Defines: + * - a name used an index + * - a regular expression allowing matching destination URLs for this disk system + * - a query URL that describes a method to query the free space from the filesystem + * - a refresh interval (seconds) defining how long do we use a + * - a targeted free space (margin) based on the free space update latency (inherent to the file system and induced by the refresh + * interval), and the expected external bandwidth from sources external to CTA. + */ +struct DiskSystem { + std::string name; + std::string fileRegexp; + std::string freeSpaceQueryURL; + uint64_t refreshInterval; + uint64_t targetedFreeSpace; + uint64_t sleepTime; + cta::common::dataStructures::EntryLog creationLog; + cta::common::dataStructures::EntryLog lastModificationLog; + std::string comment; +}; + +class DiskSystemList: public std::list<DiskSystem> { + using std::list<DiskSystem>::list; + +public: + /** Get the filesystem for a given destination URL */ + std::string getDSNAme(const std::string &fileURL) const; + + /** Get the file system parameters from a file system name */ + const DiskSystem & at(const std::string &name) const; + +private: + struct PointerAndRegex { + PointerAndRegex(const DiskSystem & dsys, const std::string &re): ds(dsys), regex(re) {} + const DiskSystem & ds; + utils::Regex regex; + }; + + mutable std::list<PointerAndRegex> m_pointersAndRegexes; + +}; + +struct DiskSystemFreeSpace { + uint64_t freeSpace; + uint64_t targetedFreeSpace; + time_t fetchTime; +}; + +class DiskSystemFreeSpaceList: public std::map<std::string, DiskSystemFreeSpace> { +public: + DiskSystemFreeSpaceList(DiskSystemList &diskSystemList): m_systemList(diskSystemList) {} + void fetchDiskSystemFreeSpace(const std::set<std::string> &diskSystems, log::LogContext & lc); + const DiskSystemList &getDiskSystemList() { return m_systemList; } +private: + DiskSystemList &m_systemList; + uint64_t fetchEosFreeSpace(const std::string & instanceAddress, log::LogContext & lc); + uint64_t fetchConstantFreeSpace(const std::string & instanceAddress, log::LogContext & lc); +}; + +}} // namespace cta::common + diff --git a/objectstore/Agent.cpp b/objectstore/Agent.cpp index 89cbe6154614afa3925aec59a431a8241b071c7d..4e6c3e91ef2905ba2af588bfa448e79245d13817 100644 --- a/objectstore/Agent.cpp +++ b/objectstore/Agent.cpp @@ -45,6 +45,7 @@ void cta::objectstore::Agent::initialize() { m_payload.set_timeout_us(120*1000*1000); m_payload.set_description(""); m_payload.set_being_garbage_collected(false); + m_payload.set_gc_needed(false); m_payloadInterpreted = true; } @@ -149,6 +150,17 @@ void cta::objectstore::Agent::setBeingGarbageCollected() { m_payload.set_being_garbage_collected(true); } +void cta::objectstore::Agent::setNeedsGarbageCollection() { + checkPayloadWritable(); + m_payload.set_gc_needed(true); +} + +bool cta::objectstore::Agent::needsGarbageCollection() { + checkPayloadReadable(); + if (!m_payload.has_gc_needed()) return false; + return m_payload.gc_needed(); +} + void cta::objectstore::Agent::garbageCollect(const std::string& presumedOwner, AgentReference & agentReference, log::LogContext & lc, cta::catalogue::Catalogue & catalogue) { checkPayloadWritable(); diff --git a/objectstore/Agent.hpp b/objectstore/Agent.hpp index da25916118e05e87baab7a875eb2f7a46239b94a..de371d1134ab34c6db3dafb780011164a185f627 100644 --- a/objectstore/Agent.hpp +++ b/objectstore/Agent.hpp @@ -62,6 +62,10 @@ public: bool isBeingGarbageCollected(); + void setNeedsGarbageCollection(); + + bool needsGarbageCollection(); + void garbageCollect(const std::string &presumedOwner, AgentReference & agentReference, log::LogContext & lc, cta::catalogue::Catalogue & catalogue) override; diff --git a/objectstore/AgentWatchdog.hpp b/objectstore/AgentWatchdog.hpp index 9f1a157a1913742a15996eeb6c34d488ea7a0a53..0ae9af672bb34e8dd8980078e158c48cd70b43ab 100644 --- a/objectstore/AgentWatchdog.hpp +++ b/objectstore/AgentWatchdog.hpp @@ -25,25 +25,28 @@ namespace cta { namespace objectstore { class AgentWatchdog { public: AgentWatchdog(const std::string & name, Backend & os): m_agent(name, os), - m_heartbeatCounter(readHeartbeat()) { + m_heartbeatCounter(readGCData().heartbeat) { m_agent.fetchNoLock(); m_timeout = m_agent.getTimeout(); } bool checkAlive() { - uint64_t newHeartBeatCount; - try { - newHeartBeatCount = readHeartbeat(); + struct gcData newGCData; + try { + newGCData = readGCData(); } catch (Backend::NoSuchObject &) { // The agent could be gone. This is not an error. Mark it as alive, // and will be trimmed later. return true; } auto timer = m_timer.secs(); - if (newHeartBeatCount == m_heartbeatCounter && timer > m_timeout) + // If GC is required, it's easy... + if (newGCData.needsGC) return false; + // If heartbeat has not moved for more than the timeout, we declare the agent dead. + if (newGCData.heartbeat == m_heartbeatCounter && timer > m_timeout) return false; - if (newHeartBeatCount != m_heartbeatCounter) { - m_heartbeatCounter = newHeartBeatCount; + if (newGCData.heartbeat != m_heartbeatCounter) { + m_heartbeatCounter = newGCData.heartbeat; m_timer.reset(); } return true; @@ -51,7 +54,9 @@ public: std::list<log::Param> getDeadAgentDetails() { std::list<log::Param> ret; - ret.push_back(log::Param("currentHeartbeat", readHeartbeat())); + auto gcData = readGCData(); + ret.push_back(log::Param("currentHeartbeat", gcData.heartbeat)); + ret.push_back(log::Param("GCRequested", gcData.needsGC?"true":"false")); ret.push_back(log::Param("timeout", m_timeout)); ret.push_back(log::Param("timer", m_timer.secs())); ret.push_back(log::Param("heartbeatAtTimerStart", m_heartbeatCounter)); @@ -72,9 +77,16 @@ private: uint64_t m_heartbeatCounter; double m_timeout; - uint64_t readHeartbeat() { + struct gcData { + uint64_t heartbeat; + bool needsGC; + }; + struct gcData readGCData() { m_agent.fetchNoLock(); - return m_agent.getHeartbeatCount(); + struct gcData ret; + ret.heartbeat = m_agent.getHeartbeatCount(); + ret.needsGC = m_agent.needsGarbageCollection(); + return ret; } }; diff --git a/objectstore/AlgorithmsTest.cpp b/objectstore/AlgorithmsTest.cpp index a4efb61965faf26aa9a942ef44297fd4aa51987b..9505536bc402b3cd90e99c26cf2f1c5182eff82e 100644 --- a/objectstore/AlgorithmsTest.cpp +++ b/objectstore/AlgorithmsTest.cpp @@ -35,7 +35,7 @@ namespace unitTests { void fillRetrieveRequests( - typename cta::objectstore::ContainerAlgorithms<cta::objectstore::RetrieveQueue,cta::objectstore::RetrieveQueueToTransferForUser>::InsertedElement::list &requests, + typename cta::objectstore::ContainerAlgorithms<cta::objectstore::RetrieveQueue,cta::objectstore::RetrieveQueueToTransfer>::InsertedElement::list &requests, std::list<std::unique_ptr<cta::objectstore::RetrieveRequest> >& requestPtrs, //List to avoid memory leak on ArchiveQueueAlgorithms test cta::objectstore::BackendVFS &be, cta::objectstore::AgentReference &agentRef) @@ -75,8 +75,8 @@ void fillRetrieveRequests( rqc.mountPolicy.retrieveMinRequestAge = 1; rqc.mountPolicy.retrievePriority = 1; requestPtrs.emplace_back(new cta::objectstore::RetrieveRequest(rrAddr, be)); - requests.emplace_back(ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser>::InsertedElement{ - requestPtrs.back().get(), 1, i, 667, mp, serializers::RetrieveJobStatus::RJS_ToTransferForUser, cta::nullopt + requests.emplace_back(ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer>::InsertedElement{ + requestPtrs.back().get(), 1, i, 667, mp, serializers::RetrieveJobStatus::RJS_ToTransfer, cta::nullopt, cta::nullopt }); auto &rr = *requests.back().retrieveRequest; rr.initialize(); @@ -189,7 +189,7 @@ TEST(ObjectStore, RetrieveQueueAlgorithms) { agent.initialize(); agent.insertAndRegisterSelf(lc); std::list<std::unique_ptr<RetrieveRequest> > requestsPtrs; - ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser>::InsertedElement::list requests; + ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer>::InsertedElement::list requests; fillRetrieveRequests(requests, requestsPtrs, be, agentRef); //memory leak here { @@ -208,18 +208,18 @@ TEST(ObjectStore, RetrieveQueueAlgorithms) { rel2.release(); agent2.initialize(); agent2.insertAndRegisterSelf(lc); - ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser>::InsertedElement::list requests2; + ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer>::InsertedElement::list requests2; std::list<std::unique_ptr<RetrieveRequest> > requestsPtrs2; fillRetrieveRequests(requests2, requestsPtrs2,be2, agentRef2); auto a1 = agentRef2.getAgentAddress(); auto a2 = agentRef2.getAgentAddress(); - ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> retrieveAlgos2(be2, agentRef2); + ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> retrieveAlgos2(be2, agentRef2); retrieveAlgos2.referenceAndSwitchOwnershipIfNecessary("VID", a2, a1, requests2, lc); } - ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> retrieveAlgos(be, agentRef); + ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> retrieveAlgos(be, agentRef); try { ASSERT_EQ(requests.size(), 10); @@ -227,19 +227,19 @@ TEST(ObjectStore, RetrieveQueueAlgorithms) { agentRef.getAgentAddress(), requests, lc); // Now get the requests back - ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PopCriteria popCriteria; + ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::PopCriteria popCriteria; popCriteria.bytes = std::numeric_limits<decltype(popCriteria.bytes)>::max(); popCriteria.files = 100; auto poppedJobs = retrieveAlgos.popNextBatch("VID", popCriteria, lc); ASSERT_EQ(poppedJobs.summary.files, 10); // Validate that the summary has the same information as the popped elements - ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PoppedElementsSummary s; + ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::PoppedElementsSummary s; for(auto &e: poppedJobs.elements) { - s += ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::getElementSummary(e); + s += ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::getElementSummary(e); } ASSERT_EQ(s, poppedJobs.summary); - } catch (ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::OwnershipSwitchFailure & ex) { + } catch (ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::OwnershipSwitchFailure & ex) { for (auto & e: ex.failedElements) { try { throw e.failure; diff --git a/objectstore/BackendPopulator.cpp b/objectstore/BackendPopulator.cpp index 3a7f71a1af683f77cd2ca404b42be1b19901d246..7c6d84ceb5e74c0ce857acfedf5dc881baefe769 100644 --- a/objectstore/BackendPopulator.cpp +++ b/objectstore/BackendPopulator.cpp @@ -66,6 +66,8 @@ BackendPopulator::~BackendPopulator() throw() { agent.fetch(); if (m_leaveNonEmptyAgentBehind && !agent.isEmpty()) { cta::log::ScopedParamContainer params(m_lc); + agent.setNeedsGarbageCollection(); + agent.commit(); params.add("agentObject", agent.getAddressIfSet()) .add("ownedObjectCount", agent.getOwnershipList().size()); m_lc.log(log::WARNING, "In BackendPopulator::~BackendPopulator(): not deleting non-empty agent object, left for garbage collection."); diff --git a/objectstore/CMakeLists.txt b/objectstore/CMakeLists.txt index d30eb6aa7f687da85aebd42283ea7f34644a7115..60e01f8ff911ff290aa55f2d4723e64c909b6f89 100644 --- a/objectstore/CMakeLists.txt +++ b/objectstore/CMakeLists.txt @@ -80,7 +80,6 @@ add_library (ctaobjectstore SHARED RetrieveQueueFailedAlgorithms.cpp RetrieveQueueToReportToRepackForSuccessAlgorithms.cpp RetrieveQueueToReportToRepackForFailureAlgorithms.cpp - RetrieveQueueToTransferForRepackAlgorithms.cpp JobQueueType.cpp Sorter.cpp ArchiveRequest.cpp diff --git a/objectstore/DriveState.cpp b/objectstore/DriveState.cpp index df47cb6caaaa0f3f159bf7f2b9019d02d1d8e0c1..b175f8951675e2577b6f65023337a572a11a1890 100644 --- a/objectstore/DriveState.cpp +++ b/objectstore/DriveState.cpp @@ -268,11 +268,78 @@ void DriveState::setConfig(const cta::tape::daemon::TapedConfiguration& tapedCon fillConfig(config->fileCatalogConfigFile); } +//------------------------------------------------------------------------------ +// DriveState::setTpConfig()) +//------------------------------------------------------------------------------ void DriveState::setTpConfig(const cta::tape::daemon::TpconfigLine& configLine){ m_payload.set_dev_file_name(configLine.devFilename); m_payload.set_raw_library_slot(configLine.rawLibrarySlot); } +//------------------------------------------------------------------------------ +// DriveState::getDiskSpaceReservations()) +//------------------------------------------------------------------------------ +std::map<std::string, uint64_t> DriveState::getDiskSpaceReservations() { + checkHeaderReadable(); + std::map<std::string, uint64_t> ret; + for (auto &dsr: m_payload.disk_space_reservations()) { + ret[dsr.disk_system_name()] = dsr.reserved_bytes(); + } + return ret; +} + +//------------------------------------------------------------------------------ +// DriveState::addDiskSpaceReservation()) +//------------------------------------------------------------------------------ +void DriveState::addDiskSpaceReservation(const std::string& diskSystemName, uint64_t bytes) { + checkPayloadWritable(); + for (auto dsr: *m_payload.mutable_disk_space_reservations()) { + if (dsr.disk_system_name() == diskSystemName) { + dsr.set_reserved_bytes(dsr.reserved_bytes() + bytes); + return; + } + } + auto * newDsr = m_payload.mutable_disk_space_reservations()->Add(); + newDsr->set_disk_system_name(diskSystemName); + newDsr->set_reserved_bytes(bytes); +} + +//------------------------------------------------------------------------------ +// DriveState::substractDiskSpaceReservation()) +//------------------------------------------------------------------------------ +void DriveState::substractDiskSpaceReservation(const std::string& diskSystemName, uint64_t bytes) { + checkPayloadWritable(); + size_t index=0; + for (auto dsr: *m_payload.mutable_disk_space_reservations()) { + if (dsr.disk_system_name() == diskSystemName) { + if (bytes > dsr.reserved_bytes()) + throw NegativeDiskSpaceReservationReached( + "In DriveState::substractDiskSpaceReservation(): we would reach a negative reservation size."); + dsr.set_reserved_bytes(dsr.reserved_bytes() - bytes); + if (!dsr.reserved_bytes()) { + // We can remove this entry from the list. + auto * mdsr = m_payload.mutable_disk_space_reservations(); + mdsr->SwapElements(index, mdsr->size()-1); + mdsr->RemoveLast(); + } + return; + } else { + ++index; + } + } + if (bytes) + throw NegativeDiskSpaceReservationReached( + "In DriveState::substractDiskSpaceReservation(): Trying to substract bytes without previous reservation."); +} + +//------------------------------------------------------------------------------ +// DriveState::resetDiskSpaceReservation()) +//------------------------------------------------------------------------------ +void DriveState::resetDiskSpaceReservation() { + checkPayloadWritable(); + m_payload.mutable_disk_space_reservations()->Clear(); +} + //------------------------------------------------------------------------------ // DriveState::dump()) //------------------------------------------------------------------------------ @@ -286,10 +353,13 @@ std::string DriveState::dump() { return headerDump; } +//------------------------------------------------------------------------------ +// DriveState::commit() +//------------------------------------------------------------------------------ void DriveState::commit(){ checkPayloadWritable(); m_payload.set_cta_version(CTA_VERSION); ObjectOps<serializers::DriveState, serializers::DriveState_t>::commit(); } -}} // namespace cta::objectstore \ No newline at end of file +}} // namespace cta::objectstore diff --git a/objectstore/DriveState.hpp b/objectstore/DriveState.hpp index 636622761fd096f2c0648e262cdd846517d9ae7f..ab405df93a61b9cfc812bfd543f6fdd18ce9c1e0 100644 --- a/objectstore/DriveState.hpp +++ b/objectstore/DriveState.hpp @@ -46,6 +46,13 @@ public: // Data access cta::common::dataStructures::DriveState getState(); void setState(cta::common::dataStructures::DriveState & state); + + std::map<std::string, uint64_t> getDiskSpaceReservations(); + void addDiskSpaceReservation(const std::string & diskSystemName, uint64_t bytes); + CTA_GENERATE_EXCEPTION_CLASS(NegativeDiskSpaceReservationReached); + void substractDiskSpaceReservation(const std::string & diskSystemName, uint64_t bytes); + void resetDiskSpaceReservation(); + void setConfig(const cta::tape::daemon::TapedConfiguration &tapedConfiguration); void setTpConfig(const cta::tape::daemon::TpconfigLine &tpConfigLine); /** diff --git a/objectstore/GarbageCollector.cpp b/objectstore/GarbageCollector.cpp index 89d7c4a9546cbbd2ae8ed2241a447c79f1d4de8c..052f4bb2098d62aec6c2664243bc89c31c6499da 100644 --- a/objectstore/GarbageCollector.cpp +++ b/objectstore/GarbageCollector.cpp @@ -338,7 +338,7 @@ void GarbageCollector::OwnedObjectSorter::sortFetchedObjects(Agent& agent, std:: std::set<std::string> candidateVids; bool disabledTape = rr->getRepackInfo().forceDisabledTape; for (auto & j: rr->dumpJobs()) { - if(j.status==RetrieveJobStatus::RJS_ToTransferForUser) { + if(j.status==RetrieveJobStatus::RJS_ToTransfer) { for (auto &tf: rr->getArchiveFile().tapeFiles) { if ((tf.copyNb == j.copyNb) && (tf.supersededByVid.empty())) candidateVids.insert(tf.vid); @@ -615,7 +615,7 @@ void GarbageCollector::OwnedObjectSorter::lockFetchAndUpdateRetrieveJobs(Agent& for (auto &tf: rr->getArchiveFile().tapeFiles) { if (tf.vid == vid) { jta.push_back({tf.copyNb, tf.fSeq, rr->getAddressIfSet(), rr->getArchiveFile().fileSize, - rr->getRetrieveFileQueueCriteria().mountPolicy, rr->getEntryLog().time, rr->getActivity()}); + rr->getRetrieveFileQueueCriteria().mountPolicy, rr->getEntryLog().time, rr->getActivity(), rr->getDiskSystemName()}); } } } diff --git a/objectstore/GarbageCollectorTest.cpp b/objectstore/GarbageCollectorTest.cpp index 1d3731caa12a291e0669b5aad0862cc1bcbf7afa..e9ff8227f4bd415f4d4068ea0e4f368b7b83b7cb 100644 --- a/objectstore/GarbageCollectorTest.cpp +++ b/objectstore/GarbageCollectorTest.cpp @@ -603,7 +603,8 @@ TEST(ObjectStore, GarbageCollectorRetrieveRequest) { cta::objectstore::ScopedExclusiveLock rql(rq); rq.fetch(); std::list <cta::objectstore::RetrieveQueue::JobToAdd> jta; - jta.push_back({1,rqc.archiveFile.tapeFiles.front().fSeq, rr.getAddressIfSet(), rqc.archiveFile.fileSize, rqc.mountPolicy, sReq.creationLog.time, cta::nullopt}); + jta.push_back({1,rqc.archiveFile.tapeFiles.front().fSeq, rr.getAddressIfSet(), rqc.archiveFile.fileSize, rqc.mountPolicy, + sReq.creationLog.time, cta::nullopt, cta::nullopt}); rq.addJobsAndCommit(jta, agentRef, lc); } if (pass < 5) { pass++; continue; } @@ -1163,7 +1164,7 @@ TEST(ObjectStore, GarbageCollectorRetrieveAllStatusesAndQueues) { sReq.archiveFileID = rqc.archiveFile.archiveFileID; sReq.creationLog.time=time(nullptr); rr.setSchedulerRequest(sReq); - rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransferForUser); + rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransfer); rr.setOwner(agentToTransferForUser.getAddressIfSet()); rr.setActiveCopyNumber(0); rr.insert(); @@ -1632,7 +1633,7 @@ TEST(ObjectStore, GarbageCollectorRetrieveRequestRepackDisabledTape){ sReq.archiveFileID = rqc.archiveFile.archiveFileID; sReq.creationLog.time=time(nullptr); rr.setSchedulerRequest(sReq); - rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransferForUser); + rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransfer); rr.setOwner(agentToTransferForUser.getAddressIfSet()); rr.setActiveCopyNumber(0); @@ -1693,7 +1694,7 @@ TEST(ObjectStore, GarbageCollectorRetrieveRequestRepackDisabledTape){ cta::objectstore::ScopedExclusiveLock sel(rr); rr.fetch(); rr.setOwner(agentRefToTransferDisabledTapeAutoGc.getAgentAddress()); - rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransferForUser); + rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransfer); rr.commit(); agentRefToTransferDisabledTapeAutoGc.addToOwnership(rr.getAddressIfSet(),be); diff --git a/objectstore/JobQueueType.hpp b/objectstore/JobQueueType.hpp index 4280616e4d1b6013fe09d3e98b699559d7ed0785..2557f6da5d75cfac3f57eb0644936a1051059150 100644 --- a/objectstore/JobQueueType.hpp +++ b/objectstore/JobQueueType.hpp @@ -21,6 +21,13 @@ #include <string> namespace cta { namespace objectstore { -enum class JobQueueType { JobsToTransferForUser, FailedJobs, JobsToReportToUser, JobsToReportToRepackForSuccess, JobsToReportToRepackForFailure, JobsToTransferForRepack }; +enum class JobQueueType { + JobsToTransferForUser, + FailedJobs, + JobsToReportToUser, + JobsToReportToRepackForSuccess, + JobsToReportToRepackForFailure, + JobsToTransferForRepack +}; std::string toString(JobQueueType queueType); }} // namespace cta::objectstore \ No newline at end of file diff --git a/objectstore/ObjectOps.hpp b/objectstore/ObjectOps.hpp index aceb9d623037c760c513ebb87833858bf8301ec3..dfa757b2617f5d4bcdcc4376e2a9f39d77200e6e 100644 --- a/objectstore/ObjectOps.hpp +++ b/objectstore/ObjectOps.hpp @@ -43,7 +43,7 @@ struct ArchiveQueueToReportToRepackForSuccess; struct ArchiveQueueFailed; struct ArchiveQueueToTransferForRepack; struct RetrieveQueue; -struct RetrieveQueueToTransferForUser; +struct RetrieveQueueToTransfer; struct RetrieveQueueToReportForUser; struct RetrieveQueueFailed; struct RetrieveQueueToReportToRepackForSuccess; @@ -64,7 +64,7 @@ class ObjectOpsBase { friend ContainerTraits<ArchiveQueue,ArchiveQueueToTransferForRepack>; friend ContainerTraits<ArchiveQueue,ArchiveQueueToReportToRepackForFailure>; friend ContainerTraits<ArchiveQueue,ArchiveQueueToReportToRepackForSuccess>; - friend ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>; + friend ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>; friend ContainerTraits<RetrieveQueue,RetrieveQueueToReportForUser>; friend ContainerTraits<RetrieveQueue,RetrieveQueueFailed>; friend ContainerTraits<RetrieveQueue,RetrieveQueueToReportToRepackForSuccess>; diff --git a/objectstore/RepackRequest.cpp b/objectstore/RepackRequest.cpp index 2ce5898f2c638562d1d09112e2e16165938a867a..a766a86f2cc367be61ff2905bfc5499254c77721 100644 --- a/objectstore/RepackRequest.cpp +++ b/objectstore/RepackRequest.cpp @@ -328,26 +328,41 @@ uint64_t RepackRequest::getLastExpandedFSeq() { return m_payload.lastexpandedfseq(); } +//------------------------------------------------------------------------------ +// RepackRequest::setTotalFileToRetrieve() +//------------------------------------------------------------------------------ void RepackRequest::setTotalFileToRetrieve(const uint64_t nbFilesToRetrieve){ checkPayloadWritable(); m_payload.set_totalfilestoretrieve(nbFilesToRetrieve); } +//------------------------------------------------------------------------------ +// RepackRequest::setTotalBytesToRetrieve() +//------------------------------------------------------------------------------ void RepackRequest::setTotalBytesToRetrieve(const uint64_t nbBytesToRetrieve){ checkPayloadWritable(); m_payload.set_totalbytestoretrieve(nbBytesToRetrieve); } +//------------------------------------------------------------------------------ +// RepackRequest::setTotalFileToArchive() +//------------------------------------------------------------------------------ void RepackRequest::setTotalFileToArchive(const uint64_t nbFilesToArchive){ checkPayloadWritable(); m_payload.set_totalfilestoarchive(nbFilesToArchive); } +//------------------------------------------------------------------------------ +// RepackRequest::setTotalBytesToArchive() +//------------------------------------------------------------------------------ void RepackRequest::setTotalBytesToArchive(const uint64_t nbBytesToArchive) { checkPayloadWritable(); m_payload.set_totalbytestoarchive(nbBytesToArchive); - } +} +//------------------------------------------------------------------------------ +// RepackRequest::getTotalStatsFile() +//------------------------------------------------------------------------------ cta::SchedulerDatabase::RepackRequest::TotalStatsFiles RepackRequest::getTotalStatsFile() { checkPayloadReadable(); cta::SchedulerDatabase::RepackRequest::TotalStatsFiles ret; diff --git a/objectstore/RetrieveQueue.cpp b/objectstore/RetrieveQueue.cpp index 755e48550338480eb412a9098909741e884246d1..e6239c1472c51156193e30e2437af76726ca7cf7 100644 --- a/objectstore/RetrieveQueue.cpp +++ b/objectstore/RetrieveQueue.cpp @@ -214,6 +214,19 @@ std::string RetrieveQueue::getVid() { return m_payload.vid(); } +void RetrieveQueue::resetSleepForFreeSpaceStartTime() { + checkPayloadWritable(); + m_payload.clear_sleep_for_free_space_since(); + m_payload.clear_disk_system_slept_for(); +} + +void RetrieveQueue::setSleepForFreeSpaceStartTimeAndName(time_t time, const std::string & diskSystemName, uint64_t sleepTime) { + checkPayloadWritable(); + m_payload.set_sleep_for_free_space_since((uint64_t)time); + m_payload.set_disk_system_slept_for(diskSystemName); + m_payload.set_sleep_time(sleepTime); +} + std::string RetrieveQueue::dump() { checkPayloadReadable(); google::protobuf::util::JsonPrintOptions options; @@ -531,7 +544,7 @@ auto RetrieveQueue::addJobsIfNecessaryAndCommit(std::list<JobToAdd> & jobsToAdd, } shardsDumps.emplace_back(std::list<JobDump>()); for (auto & j: s->dumpJobs()) { - shardsDumps.back().emplace_back(JobDump({j.address, j.copyNb, j.size})); + shardsDumps.back().emplace_back(JobDump({j.address, j.copyNb, j.size, j.activityDescription, j.diskSystemName})); } nextShard: s++; @@ -577,6 +590,13 @@ RetrieveQueue::JobsSummary RetrieveQueue::getJobsSummary() { for (auto ra: retrieveActivityCountMap.getActivities(ret.priority)) { ret.activityCounts.push_back({ra.diskInstanceName, ra.activity, ra.weight, ra.count}); } + if (m_payload.has_sleep_for_free_space_since()) { + JobsSummary::SleepInfo si; + si.diskSystemSleptFor = m_payload.disk_system_slept_for(); + si.sleepStartTime = m_payload.sleep_for_free_space_since(); + si.sleepTime = m_payload.sleep_time(); + ret.sleepInfo = si; + } } else { ret.maxDrivesAllowed = 0; ret.priority = 0; @@ -606,7 +626,7 @@ auto RetrieveQueue::dumpJobs() -> std::list<JobDump> { goto nextShard; } for (auto & j: s->dumpJobs()) { - ret.emplace_back(JobDump{j.address, j.copyNb, j.size}); + ret.emplace_back(JobDump{j.address, j.copyNb, j.size, j.activityDescription, j.diskSystemName}); } nextShard: s++; sf++; @@ -614,7 +634,7 @@ auto RetrieveQueue::dumpJobs() -> std::list<JobDump> { return ret; } -auto RetrieveQueue::getCandidateList(uint64_t maxBytes, uint64_t maxFiles, std::set<std::string> retrieveRequestsToSkip) -> CandidateJobList { +auto RetrieveQueue::getCandidateList(uint64_t maxBytes, uint64_t maxFiles, const std::set<std::string> & retrieveRequestsToSkip, const std::set<std::string> & diskSystemsToSkip) -> CandidateJobList { checkPayloadReadable(); CandidateJobList ret; for(auto & rqsp: m_payload.retrievequeueshards()) { @@ -623,7 +643,8 @@ auto RetrieveQueue::getCandidateList(uint64_t maxBytes, uint64_t maxFiles, std:: // Fetch the shard RetrieveQueueShard rqs(rqsp.address(), m_objectStore); rqs.fetchNoLock(); - auto shardCandidates = rqs.getCandidateJobList(maxBytes - ret.candidateBytes, maxFiles - ret.candidateFiles, retrieveRequestsToSkip); + auto shardCandidates = rqs.getCandidateJobList(maxBytes - ret.candidateBytes, maxFiles - ret.candidateFiles, + retrieveRequestsToSkip, diskSystemsToSkip); ret.candidateBytes += shardCandidates.candidateBytes; ret.candidateFiles += shardCandidates.candidateFiles; // We overwrite the remaining values each time as the previous diff --git a/objectstore/RetrieveQueue.hpp b/objectstore/RetrieveQueue.hpp index d9566754e42c0a23d05491452a6470d247d37674..9120d1915b5c380f3e9b397b465fe509a01eadb0 100644 --- a/objectstore/RetrieveQueue.hpp +++ b/objectstore/RetrieveQueue.hpp @@ -66,7 +66,8 @@ public: uint64_t fileSize; cta::common::dataStructures::MountPolicy policy; time_t startTime; - optional<RetrieveActivityDescription> activityDescription; + optional<RetrieveActivityDescription> activityDescription; + optional<std::string> diskSystemName; }; void addJobsAndCommit(std::list<JobToAdd> & jobsToAdd, AgentReference & agentReference, log::LogContext & lc); // This version will check for existence of the job in the queue before @@ -91,12 +92,24 @@ public: uint64_t count; }; std::list<ActivityCount> activityCounts; + struct SleepInfo { + time_t sleepStartTime; + std::string diskSystemSleptFor; + uint64_t sleepTime; + }; + optional<SleepInfo> sleepInfo; }; JobsSummary getJobsSummary(); struct JobDump { std::string address; uint32_t copyNb; uint64_t size; + struct ActivityDescription { + std::string diskInstanceName; + std::string activity; + }; + optional<ActivityDescription> activity; + optional<std::string> diskSystemName; }; std::list<JobDump> dumpJobs(); struct CandidateJobList { @@ -108,7 +121,9 @@ public: }; // The set of retrieve requests to skip are requests previously identified by the caller as bad, // which still should be removed from the queue. They will be disregarded from listing. - CandidateJobList getCandidateList(uint64_t maxBytes, uint64_t maxFiles, std::set<std::string> retrieveRequestsToSkip); + CandidateJobList getCandidateList(uint64_t maxBytes, uint64_t maxFiles, const std::set<std::string> & retrieveRequestsToSkip, + const std::set<std::string> & diskSystemsToSkip); + //! Return a summary of the number of jobs and number of bytes in the queue CandidateJobList getCandidateSummary(); @@ -117,6 +132,11 @@ public: // -- Generic parameters std::string getVid(); + // Support for sleep waiting free space (back pressure). + // This data is queried through getJobsSummary(). + void setSleepForFreeSpaceStartTimeAndName(time_t time, const std::string & diskSystemName, uint64_t sleepTime); + void resetSleepForFreeSpaceStartTime(); + private: struct ShardForAddition { bool newShard=false; @@ -157,12 +177,11 @@ private: uint64_t m_maxShardSize = c_defaultMaxShardSize; }; -class RetrieveQueueToTransferForUser : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; +class RetrieveQueueToTransfer : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; class RetrieveQueueToReportForUser : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; class RetrieveQueueFailed : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; class RetrieveQueueToReportToRepackForSuccess : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; class RetrieveQueueToReportToRepackForFailure: public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; -class RetrieveQueueToTransferForRepack : public RetrieveQueue { using RetrieveQueue::RetrieveQueue; }; }} diff --git a/objectstore/RetrieveQueueAlgorithms.hpp b/objectstore/RetrieveQueueAlgorithms.hpp index 45dd12db5e3914b59ba2ae0c082e95f00d9530f0..2146f17783d857a81e989b6c2731bcafb973c8fe 100644 --- a/objectstore/RetrieveQueueAlgorithms.hpp +++ b/objectstore/RetrieveQueueAlgorithms.hpp @@ -31,7 +31,7 @@ struct ContainerTraits<RetrieveQueue,C> ContainerSummary() : RetrieveQueue::JobsSummary() {} ContainerSummary(const RetrieveQueue::JobsSummary &c) : RetrieveQueue::JobsSummary({c.jobs,c.bytes,c.oldestJobStartTime,c.priority, - c.minRetrieveRequestAge,c.maxDrivesAllowed,c.activityCounts}) {} + c.minRetrieveRequestAge,c.maxDrivesAllowed,c.activityCounts, nullopt}) {} void addDeltaToLog(const ContainerSummary&, log::ScopedParamContainer&) const; }; @@ -45,6 +45,7 @@ struct ContainerTraits<RetrieveQueue,C> cta::common::dataStructures::MountPolicy policy; serializers::RetrieveJobStatus status; optional<RetrieveActivityDescription> activityDescription; + optional<std::string> diskSystemName; typedef std::list<InsertedElement> list; }; @@ -59,6 +60,8 @@ struct ContainerTraits<RetrieveQueue,C> std::string errorReportURL; SchedulerDatabase::RetrieveJob::ReportType reportType; RetrieveRequest::RepackInfo repackInfo; + optional<RetrieveQueue::JobDump::ActivityDescription> activity; + optional<std::string> diskSystemName; }; struct PoppedElementsSummary; struct PopCriteria { @@ -279,7 +282,7 @@ addReferencesAndCommit(Container &cont, typename InsertedElement::list &elemMemC std::list<RetrieveQueue::JobToAdd> jobsToAdd; for (auto &e : elemMemCont) { RetrieveRequest &rr = *e.retrieveRequest; - jobsToAdd.push_back({e.copyNb, e.fSeq, rr.getAddressIfSet(), e.filesize, e.policy, ::time(nullptr), e.activityDescription}); + jobsToAdd.push_back({e.copyNb, e.fSeq, rr.getAddressIfSet(), e.filesize, e.policy, ::time(nullptr), e.activityDescription, e.diskSystemName}); } cont.addJobsAndCommit(jobsToAdd, agentRef, lc); } @@ -292,7 +295,7 @@ addReferencesIfNecessaryAndCommit(Container& cont, typename InsertedElement::lis std::list<RetrieveQueue::JobToAdd> jobsToAdd; for (auto &e : elemMemCont) { RetrieveRequest &rr = *e.retrieveRequest; - jobsToAdd.push_back({e.copyNb, e.fSeq, rr.getAddressIfSet(), e.filesize, e.policy, ::time(nullptr), e.activityDescription}); + jobsToAdd.push_back({e.copyNb, e.fSeq, rr.getAddressIfSet(), e.filesize, e.policy, ::time(nullptr), e.activityDescription, e.diskSystemName}); } cont.addJobsIfNecessaryAndCommit(jobsToAdd, agentRef, lc); } @@ -387,6 +390,11 @@ switchElementsOwnership(PoppedElementsBatch &poppedElementBatch, const Container e.archiveFile = u.get()->getArchiveFile(); e.rr = u.get()->getRetrieveRequest(); e.repackInfo = u.get()->getRepackInfo(); + auto & rad = u.get()->getRetrieveActivityDescription(); + if (rad) { + e.activity = RetrieveQueue::JobDump::ActivityDescription{ rad.value().diskInstanceName, rad.value().activity }; + } + e.diskSystemName = u.get()->getDiskSystemName(); switch(u.get()->getJobStatus()) { case serializers::RetrieveJobStatus::RJS_ToReportToUserForFailure: e.reportType = SchedulerDatabase::RetrieveJob::ReportType::FailureReport; @@ -408,7 +416,19 @@ bool ContainerTraits<RetrieveQueue,C>:: trimContainerIfNeeded(Container &cont, ScopedExclusiveLock &contLock, const ContainerIdentifier &cId, log::LogContext &lc) { - if(!cont.isEmpty()) return false; + if(!cont.isEmpty()) { + auto si = cont.getJobsSummary().sleepInfo; + if (si) { + log::ScopedParamContainer params(lc); + params.add("tapeVid", cId) + .add("queueObject", cont.getAddressIfSet()) + .add("diskSystemSleptFor", si.value().diskSystemSleptFor); + lc.log(log::INFO, "In ContainerTraits<RetrieveQueue,C>::trimContainerIfNeeded(): non-empty queue is sleeping"); + // We fake the fact that we trimed the queue for compatibility with previous algorithms (a sleeping queue is like gone at this point). + return true; + } + return false; + } // The current implementation is done unlocked contLock.release(); try { @@ -437,7 +457,7 @@ trimContainerIfNeeded(Container &cont, ScopedExclusiveLock &contLock, // RetrieveQueue full specialisations for ContainerTraits. template<> -struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PopCriteria { +struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::PopCriteria { uint64_t files; uint64_t bytes; PopCriteria(uint64_t f = 0, uint64_t b = 0) : files(f), bytes(b) {} @@ -447,12 +467,20 @@ struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PopCriteri files -= pes.files; return *this; } + struct DiskSystemToSkip { + std::string name; + uint64_t sleepTime; + bool operator<(const DiskSystemToSkip o) const { return name < o.name; } + }; + std::set<DiskSystemToSkip> diskSystemsToSkip; }; template<> -struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PoppedElementsSummary { +struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::PoppedElementsSummary { uint64_t files; uint64_t bytes; + bool diskSystemFull = false; + std::string fullDiskSystem; PoppedElementsSummary(uint64_t f = 0, uint64_t b = 0) : files(f), bytes(b) {} bool operator==(const PoppedElementsSummary &pes) const { return bytes == pes.bytes && files == pes.files; @@ -478,12 +506,18 @@ struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PoppedElem template<typename C> auto ContainerTraits<RetrieveQueue,C>:: -getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, ElementsToSkipSet &elemtsToSkip, +getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, ElementsToSkipSet &elementsToSkip, log::LogContext &lc) -> PoppedElementsBatch { PoppedElementsBatch ret; - auto candidateJobsFromQueue = cont.getCandidateList(std::numeric_limits<uint64_t>::max(), unfulfilledCriteria.files, elemtsToSkip); + auto candidateJobsFromQueue = cont.getCandidateList(std::numeric_limits<uint64_t>::max(), unfulfilledCriteria.files, + elementsToSkip, + // This parameter is needed only in the specialized version: + // auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::getPoppingElementsCandidates + // We provide an empty set here. + std::set<std::string>() + ); for(auto &cjfq : candidateJobsFromQueue.candidates) { ret.elements.emplace_back(PoppedElement{ cta::make_unique<RetrieveRequest>(cjfq.address, cont.m_objectStore), @@ -492,7 +526,7 @@ getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, common::dataStructures::ArchiveFile(), common::dataStructures::RetrieveRequest(), "", SchedulerDatabase::RetrieveJob::ReportType::NoReportRequired, - RetrieveRequest::RepackInfo() + RetrieveRequest::RepackInfo(), cjfq.activity, cjfq.diskSystemName }); ret.summary.files++; } @@ -503,7 +537,7 @@ template<typename C> const std::string ContainerTraits<RetrieveQueue,C>::c_identifierType = "tapeVid"; template<> -struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::QueueType{ +struct ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::QueueType{ objectstore::JobQueueType value = objectstore::JobQueueType::JobsToTransferForUser; }; @@ -532,4 +566,9 @@ struct ContainerTraits<RetrieveQueue, RetrieveQueueToTransferForRepack>::QueueTy objectstore::JobQueueType value = objectstore::JobQueueType::JobsToTransferForRepack; }; +template<> +auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>:: +getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, ElementsToSkipSet &elementsToSkip, + log::LogContext &lc) -> PoppedElementsBatch; + }} // namespace cta::objectstore diff --git a/objectstore/RetrieveQueueShard.cpp b/objectstore/RetrieveQueueShard.cpp index 76ebe1e5c36e03698b3c8f7894f8dbade013fe38..f28cbf33fccdf66663ff95a4d4b038a6429e1ef8 100644 --- a/objectstore/RetrieveQueueShard.cpp +++ b/objectstore/RetrieveQueueShard.cpp @@ -62,14 +62,23 @@ void RetrieveQueueShard::garbageCollect(const std::string& presumedOwner, AgentR throw exception::Exception("In RetrieveQueueShard::garbageCollect(): garbage collection should not be necessary for this type of object."); } -RetrieveQueue::CandidateJobList RetrieveQueueShard::getCandidateJobList(uint64_t maxBytes, uint64_t maxFiles, std::set<std::string> retrieveRequestsToSkip) { +RetrieveQueue::CandidateJobList RetrieveQueueShard::getCandidateJobList(uint64_t maxBytes, uint64_t maxFiles, const std::set<std::string> & retrieveRequestsToSkip, const std::set<std::string> & diskSystemsToSkip) { checkPayloadReadable(); RetrieveQueue::CandidateJobList ret; ret.remainingBytesAfterCandidates = m_payload.retrievejobstotalsize(); ret.remainingFilesAfterCandidates = m_payload.retrievejobs_size(); for (auto & j: m_payload.retrievejobs()) { - if (!retrieveRequestsToSkip.count(j.address())) { - ret.candidates.push_back({j.address(), (uint16_t)j.copynb(), j.size()}); + if (!retrieveRequestsToSkip.count(j.address()) && !diskSystemsToSkip.count(j.destination_disk_system_name())) { + ret.candidates.push_back({j.address(), (uint16_t)j.copynb(), j.size(), nullopt, nullopt}); + if (j.has_activity()) { + RetrieveQueue::JobDump::ActivityDescription ad; + ad.activity = j.activity(); + ad.diskInstanceName = j.disk_instance_name(); + ret.candidates.back().activity = ad; + } + if (j.has_destination_disk_system_name()) { + ret.candidates.back().diskSystemName = j.destination_disk_system_name(); + } ret.candidateBytes += j.size(); ret.candidateFiles ++; } @@ -104,7 +113,9 @@ auto RetrieveQueueShard::removeJobs(const std::list<std::string>& jobsToRemove) ret.removedJobs.back().size = j.size(); ret.removedJobs.back().startTime = j.starttime(); if (j.has_activity()) - ret.removedJobs.back().activityDescription = JobInfo::ActivityDescription{ j.disk_instance_name(), j.activity() }; + ret.removedJobs.back().activityDescription = RetrieveQueue::JobDump::ActivityDescription{ j.disk_instance_name(), j.activity() }; + if (j.has_destination_disk_system_name()) + ret.removedJobs.back().diskSystemName = j.destination_disk_system_name(); ret.bytesRemoved += j.size(); totalSize -= j.size(); ret.jobsRemoved++; @@ -139,9 +150,12 @@ auto RetrieveQueueShard::dumpJobs() -> std::list<JobInfo> { std::list<JobInfo> ret; for (auto &j: m_payload.retrievejobs()) { ret.emplace_back(JobInfo{j.size(), j.address(), (uint16_t)j.copynb(), j.priority(), - j.minretrieverequestage(), j.maxdrivesallowed(), (time_t)j.starttime(), j.fseq(), nullopt}); + j.minretrieverequestage(), j.maxdrivesallowed(), (time_t)j.starttime(), j.fseq(), nullopt, nullopt}); if (j.has_activity()) { - ret.back().activityDescription = JobInfo::ActivityDescription{ j.disk_instance_name(), j.activity() }; + ret.back().activityDescription = RetrieveQueue::JobDump::ActivityDescription{ j.disk_instance_name(), j.activity() }; + } + if (j.has_destination_disk_system_name()) { + ret.back().diskSystemName = j.destination_disk_system_name(); } } return ret; @@ -166,6 +180,8 @@ std::list<RetrieveQueue::JobToAdd> RetrieveQueueShard::dumpJobsToAdd() { rad.activity = j.activity(); ret.back().activityDescription = rad; } + if (j.has_destination_disk_system_name()) + ret.back().diskSystemName = j.destination_disk_system_name(); } return ret; } @@ -268,6 +284,7 @@ void RetrieveQueueShard::addJob(const RetrieveQueue::JobToAdd& jobToAdd) { j->set_disk_instance_name(jobToAdd.activityDescription.value().diskInstanceName); j->set_activity(jobToAdd.activityDescription.value().activity); } + if (jobToAdd.diskSystemName) j->set_destination_disk_system_name(jobToAdd.diskSystemName.value()); m_payload.set_retrievejobstotalsize(m_payload.retrievejobstotalsize()+jobToAdd.fileSize); // Sort the shard size_t jobIndex = m_payload.retrievejobs_size() - 1; @@ -304,6 +321,7 @@ void RetrieveQueueShard::addJobsThroughCopy(JobsToAddSet& jobsToAdd) { rjp.set_disk_instance_name(jobToAdd.activityDescription.value().diskInstanceName); rjp.set_activity(jobToAdd.activityDescription.value().activity); } + if (jobToAdd.diskSystemName) rjp.set_destination_disk_system_name(jobToAdd.diskSystemName.value()); i = serializedJobsToAdd.insert(i, rjp); totalSize+=jobToAdd.fileSize; } diff --git a/objectstore/RetrieveQueueShard.hpp b/objectstore/RetrieveQueueShard.hpp index 7da9d10d72144578e2975e25332770e85d0b489b..fa798e4b439fca79104282dcb501b2ec0b53cdec 100644 --- a/objectstore/RetrieveQueueShard.hpp +++ b/objectstore/RetrieveQueueShard.hpp @@ -53,11 +53,8 @@ public: uint64_t maxDrivesAllowed; time_t startTime; uint64_t fSeq; - struct ActivityDescription { - std::string diskInstanceName; - std::string activity; - }; - optional<ActivityDescription> activityDescription; + optional<RetrieveQueue::JobDump::ActivityDescription> activityDescription; + optional<std::string> diskSystemName; }; std::list<JobInfo> dumpJobs(); @@ -115,7 +112,8 @@ public: */ RemovalResult removeJobs(const std::list<std::string> & jobsToRemove); - RetrieveQueue::CandidateJobList getCandidateJobList(uint64_t maxBytes, uint64_t maxFiles, std::set<std::string> retrieveRequestsToSkip); + RetrieveQueue::CandidateJobList getCandidateJobList(uint64_t maxBytes, uint64_t maxFiles, + const std::set<std::string> & retrieveRequestsToSkip, const std::set<std::string> & diskSystemsToSkip); /** Re compute summaries in case they do not match the array content. */ void rebuild(); diff --git a/objectstore/RetrieveQueueTest.cpp b/objectstore/RetrieveQueueTest.cpp index 098114e6289ee943527374f1714ef091042cd2de..aa7d386797eae835bccd7a8954eaf32a7c1a7916 100644 --- a/objectstore/RetrieveQueueTest.cpp +++ b/objectstore/RetrieveQueueTest.cpp @@ -124,7 +124,8 @@ TEST(ObjectStore, RetrieveQueueShardingAndOrderingTest) { ASSERT_EQ(minStartTime, rq.getJobsSummary().oldestJobStartTime); uint64_t nextExpectedFseq=0; while (rq.getJobsSummary().jobs) { - auto candidateJobs = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 50, std::set<std::string>()); + auto candidateJobs = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 50, std::set<std::string>(), + std::set<std::string>()); std::set<std::string> jobsToSkip; std::list<std::string> jobsToDelete; for (auto &j: candidateJobs.candidates) { @@ -135,7 +136,7 @@ TEST(ObjectStore, RetrieveQueueShardingAndOrderingTest) { jobsToDelete.emplace_back(j.address); nextExpectedFseq++; } - auto candidateJobs2 = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 1, jobsToSkip); + auto candidateJobs2 = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 1, jobsToSkip, std::set<std::string>()); if (candidateJobs2.candidateFiles) { std::stringstream address; address << "someRequest-" << nextExpectedFseq; @@ -245,7 +246,8 @@ TEST(ObjectStore, RetrieveQueueActivityCounts) { ASSERT_EQ(0.2, jsB->weight); uint64_t nextExpectedFseq=0; while (rq.getJobsSummary().jobs) { - auto candidateJobs = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 50, std::set<std::string>()); + auto candidateJobs = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 50, std::set<std::string>(), + std::set<std::string>()); std::set<std::string> jobsToSkip; std::list<std::string> jobsToDelete; for (auto &j: candidateJobs.candidates) { @@ -256,7 +258,7 @@ TEST(ObjectStore, RetrieveQueueActivityCounts) { jobsToDelete.emplace_back(j.address); nextExpectedFseq++; } - auto candidateJobs2 = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 1, jobsToSkip); + auto candidateJobs2 = rq.getCandidateList(std::numeric_limits<uint64_t>::max(), 1, jobsToSkip, std::set<std::string>()); if (candidateJobs2.candidateFiles) { std::stringstream address; address << "someRequest-" << nextExpectedFseq; diff --git a/objectstore/RetrieveQueueToTransferAlgorithms.cpp b/objectstore/RetrieveQueueToTransferAlgorithms.cpp index 2d80b46b33cf75d87886f1b2572b3f7739547096..679db13a4a74bea3fff25404c189e2a7b8a105f4 100644 --- a/objectstore/RetrieveQueueToTransferAlgorithms.cpp +++ b/objectstore/RetrieveQueueToTransferAlgorithms.cpp @@ -21,10 +21,10 @@ namespace cta { namespace objectstore { template<> -const std::string ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::c_containerTypeName = "RetrieveQueueToTransferForUser"; +const std::string ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::c_containerTypeName = "RetrieveQueueToTransfer"; template<> -auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>:: +auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>:: getElementSummary(const PoppedElement &poppedElement) -> PoppedElementsSummary { PoppedElementsSummary ret; ret.bytes = poppedElement.bytes; @@ -33,20 +33,36 @@ getElementSummary(const PoppedElement &poppedElement) -> PoppedElementsSummary { } template<> -void ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>::PoppedElementsBatch:: +void ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::PoppedElementsBatch:: addToLog(log::ScopedParamContainer ¶ms) const { params.add("bytes", summary.bytes) .add("files", summary.files); } template<> -auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForUser>:: -getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, ElementsToSkipSet &elemtsToSkip, +auto ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>:: +getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, ElementsToSkipSet &elementsToSkip, log::LogContext &lc) -> PoppedElementsBatch { PoppedElementsBatch ret; - auto candidateJobsFromQueue = cont.getCandidateList(unfulfilledCriteria.bytes, unfulfilledCriteria.files, elemtsToSkip); + std::set<std::string> diskSystemsToSkipNames; + for (auto &ds: unfulfilledCriteria.diskSystemsToSkip) diskSystemsToSkipNames.insert(ds.name); + auto candidateJobsFromQueue = cont.getCandidateList(unfulfilledCriteria.bytes, unfulfilledCriteria.files, elementsToSkip, diskSystemsToSkipNames); + if (unfulfilledCriteria.diskSystemsToSkip.size() && candidateJobsFromQueue.candidates.empty() && cont.getJobsSummary().jobs && elementsToSkip.empty()) { + // We failed to find any candidates from a non empty queue, from which there are no individual elements to skip. + // this means the it is time to sleep this queue. + // We log and return empty. Caller will stop trying to pop after that. + cont.setSleepForFreeSpaceStartTimeAndName(::time(nullptr), unfulfilledCriteria.diskSystemsToSkip.begin()->name, unfulfilledCriteria.diskSystemsToSkip.begin()->sleepTime); + cont.commit(); + log::ScopedParamContainer params(lc); + params.add("tapeVid", cont.getVid()) + .add("queueObject", cont.getAddressIfSet()) + .add("diskSystemName", unfulfilledCriteria.diskSystemsToSkip.begin()->name) + .add("sleepTime", unfulfilledCriteria.diskSystemsToSkip.begin()->sleepTime); + lc.log(log::WARNING, "In ContainerTraits<RetrieveQueue,RetrieveQueueToTransfer>::getPoppingElementsCandidates(): sleeping queue due to disk system full."); + return ret; + } for(auto &cjfq : candidateJobsFromQueue.candidates) { ret.elements.emplace_back(PoppedElement{ cta::make_unique<RetrieveRequest>(cjfq.address, cont.m_objectStore), @@ -56,7 +72,9 @@ getPoppingElementsCandidates(Container &cont, PopCriteria &unfulfilledCriteria, common::dataStructures::RetrieveRequest(), "", SchedulerDatabase::RetrieveJob::ReportType::NoReportRequired, - RetrieveRequest::RepackInfo() + RetrieveRequest::RepackInfo(), + cjfq.activity, + cjfq.diskSystemName }); ret.summary.bytes += cjfq.size; ret.summary.files++; diff --git a/objectstore/RetrieveQueueToTransferForRepackAlgorithms.cpp b/objectstore/RetrieveQueueToTransferForRepackAlgorithms.cpp deleted file mode 100644 index 18e8491cc24219ed0acbfea4013ea047a0713ffd..0000000000000000000000000000000000000000 --- a/objectstore/RetrieveQueueToTransferForRepackAlgorithms.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * The CERN Tape Archive (CTA) project - * Copyright © 2018 CERN - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -#include "RetrieveQueueAlgorithms.hpp" - -namespace cta { namespace objectstore { - -template<> -const std::string ContainerTraits<RetrieveQueue,RetrieveQueueToTransferForRepack>::c_containerTypeName = "RetrieveQueueToTransferForRepack"; - -}} \ No newline at end of file diff --git a/objectstore/RetrieveRequest.cpp b/objectstore/RetrieveRequest.cpp index 672c5f5bb32066488f950ffd8ab27db34c6f33ee..2a77db5af1630508a78f6d9848e8c1d48389d107 100644 --- a/objectstore/RetrieveRequest.cpp +++ b/objectstore/RetrieveRequest.cpp @@ -87,7 +87,7 @@ void RetrieveRequest::garbageCollect(const std::string& presumedOwner, AgentRefe std::set<std::string> candidateVids; for (auto &j: m_payload.jobs()) { switch(j.status()){ - case RetrieveJobStatus::RJS_ToTransferForUser: + case RetrieveJobStatus::RJS_ToTransfer: // Find the job details in tape file for (auto &tf: m_payload.archivefile().tapefiles()) { if (tf.copynb() == j.copynb()) { @@ -160,7 +160,7 @@ queueForFailure:; { // If there is no candidate, we fail the jobs that are not yet, and queue the request as failed (on any VID). for (auto & j: *m_payload.mutable_jobs()) { - if (j.status() == RetrieveJobStatus::RJS_ToTransferForUser) { + if (j.status() == RetrieveJobStatus::RJS_ToTransfer) { j.set_status(RetrieveJobStatus::RJS_Failed); log::ScopedParamContainer params(lc); params.add("fileId", m_payload.archivefile().archivefileid()) @@ -204,7 +204,7 @@ queueForFailure:; objectstore::MountPolicySerDeser mp; std::list<RetrieveQueue::JobToAdd> jta; jta.push_back({activeCopyNb, activeFseq, getAddressIfSet(), m_payload.archivefile().filesize(), - mp, (signed)m_payload.schedulerrequest().entrylog().time(), nullopt}); + mp, (signed)m_payload.schedulerrequest().entrylog().time(), nullopt, nullopt}); if (m_payload.has_activity_weight()) { RetrieveActivityDescription activityDescription; activityDescription.priority = m_payload.activity_weight().priority(); @@ -272,7 +272,7 @@ queueForTransfer:; mp.deserialize(m_payload.mountpolicy()); std::list<RetrieveQueue::JobToAdd> jta; jta.push_back({bestTapeFile->copynb(), bestTapeFile->fseq(), getAddressIfSet(), m_payload.archivefile().filesize(), - mp, (signed)m_payload.schedulerrequest().entrylog().time(), nullopt}); + mp, (signed)m_payload.schedulerrequest().entrylog().time(), getActivity(), getDiskSystemName()}); if (m_payload.has_activity_weight()) { RetrieveActivityDescription activityDescription; activityDescription.priority = m_payload.activity_weight().priority(); @@ -348,7 +348,7 @@ void RetrieveRequest::addJob(uint32_t copyNb, uint16_t maxRetriesWithinMount, ui tf->set_totalretries(0); tf->set_maxreportretries(maxReportRetries); tf->set_totalreportretries(0); - tf->set_status(serializers::RetrieveJobStatus::RJS_ToTransferForUser); + tf->set_status(serializers::RetrieveJobStatus::RJS_ToTransfer); } //------------------------------------------------------------------------------ @@ -375,7 +375,7 @@ auto RetrieveRequest::addTransferFailure(uint32_t copyNumber, uint64_t mountId, } if(j.totalretries() < j.maxtotalretries()) { EnqueueingNextStep ret; - ret.nextStatus = serializers::RetrieveJobStatus::RJS_ToTransferForUser; + ret.nextStatus = serializers::RetrieveJobStatus::RJS_ToTransfer; if(j.retrieswithinmount() < j.maxretrieswithinmount()) // Job can try again within this mount ret.nextStep = EnqueueingNextStep::NextStep::EnqueueForTransferForUser; @@ -538,6 +538,25 @@ optional<RetrieveActivityDescription> RetrieveRequest::getActivity() { return ret; } +//------------------------------------------------------------------------------ +// RetrieveRequest::setDiskSystemName() +//------------------------------------------------------------------------------ +void RetrieveRequest::setDiskSystemName(const std::string& diskSystemName) { + checkPayloadWritable(); + m_payload.set_disk_system_name(diskSystemName); +} + +//------------------------------------------------------------------------------ +// RetrieveRequest::getDiskSystemName() +//------------------------------------------------------------------------------ +optional<std::string> RetrieveRequest::getDiskSystemName() { + checkPayloadReadable(); + optional<std::string> ret; + if (m_payload.has_disk_system_name()) + ret = m_payload.disk_system_name(); + return ret; +} + //------------------------------------------------------------------------------ // RetrieveRequest::dumpJobs() //------------------------------------------------------------------------------ @@ -608,10 +627,10 @@ bool RetrieveRequest::addJobFailure(uint32_t copyNumber, uint64_t mountId, if (j.totalretries() >= j.maxtotalretries()) { j.set_status(serializers::RetrieveJobStatus::RJS_ToReportToUserForFailure); for (auto & j2: m_payload.jobs()) - if (j2.status() == serializers::RetrieveJobStatus::RJS_ToTransferForUser) return false; + if (j2.status() == serializers::RetrieveJobStatus::RJS_ToTransfer) return false; return true; } else { - j.set_status(serializers::RetrieveJobStatus::RJS_ToTransferForUser); + j.set_status(serializers::RetrieveJobStatus::RJS_ToTransfer); return false; } } @@ -680,7 +699,7 @@ JobQueueType RetrieveRequest::getQueueType() { for (auto &j: m_payload.jobs()) { // Any job is to be transfered => To transfer switch(j.status()) { - case serializers::RetrieveJobStatus::RJS_ToTransferForUser: + case serializers::RetrieveJobStatus::RJS_ToTransfer: return JobQueueType::JobsToTransferForUser; break; case serializers::RetrieveJobStatus::RJS_ToReportToRepackForSuccess: @@ -692,8 +711,6 @@ JobQueueType RetrieveRequest::getQueueType() { break; case serializers::RetrieveJobStatus::RJS_ToReportToRepackForFailure: return JobQueueType::JobsToReportToRepackForFailure; - case serializers::RetrieveJobStatus::RJS_ToTransferForRepack: - return JobQueueType::JobsToTransferForRepack; default: break; } } @@ -701,12 +718,15 @@ JobQueueType RetrieveRequest::getQueueType() { return JobQueueType::FailedJobs; } +//------------------------------------------------------------------------------ +// RetrieveRequest::getQueueType() +//------------------------------------------------------------------------------ JobQueueType RetrieveRequest::getQueueType(uint32_t copyNb){ checkPayloadReadable(); for(auto &j: m_payload.jobs()){ if(j.copynb() == copyNb){ switch(j.status()){ - case serializers::RetrieveJobStatus::RJS_ToTransferForUser: + case serializers::RetrieveJobStatus::RJS_ToTransfer: return JobQueueType::JobsToTransferForUser; case serializers::RetrieveJobStatus::RJS_ToReportToRepackForSuccess: return JobQueueType::JobsToReportToRepackForSuccess; @@ -716,8 +736,6 @@ JobQueueType RetrieveRequest::getQueueType(uint32_t copyNb){ return JobQueueType::FailedJobs; case serializers::RetrieveJobStatus::RJS_ToReportToRepackForFailure: return JobQueueType::JobsToReportToRepackForFailure; - case serializers::RetrieveJobStatus::RJS_ToTransferForRepack: - return JobQueueType::JobsToTransferForRepack; default: return JobQueueType::FailedJobs; } @@ -731,7 +749,7 @@ JobQueueType RetrieveRequest::getQueueType(uint32_t copyNb){ //------------------------------------------------------------------------------ std::string RetrieveRequest::statusToString(const serializers::RetrieveJobStatus& status) { switch(status) { - case serializers::RetrieveJobStatus::RJS_ToTransferForUser: + case serializers::RetrieveJobStatus::RJS_ToTransfer: return "ToTransfer"; case serializers::RetrieveJobStatus::RJS_Failed: return "Failed"; @@ -779,7 +797,7 @@ auto RetrieveRequest::determineNextStep(uint32_t copyNumberUpdated, JobEvent job switch (jobEvent) { case JobEvent::TransferFailed: - if (*currentStatus != RetrieveJobStatus::RJS_ToTransferForUser) { + if (*currentStatus != RetrieveJobStatus::RJS_ToTransfer) { // Wrong status, but the context leaves no ambiguity. Just warn. log::ScopedParamContainer params(lc); params.add("event", eventToString(jobEvent)) @@ -837,7 +855,7 @@ void RetrieveRequest::updateLifecycleTiming(serializers::RetrieveRequest& payloa LifecycleTimingsSerDeser lifeCycleSerDeser; lifeCycleSerDeser.deserialize(payload.lifecycle_timings()); switch(retrieveJob.status()){ - case RetrieveJobStatus::RJS_ToTransferForUser: + case RetrieveJobStatus::RJS_ToTransfer: if(retrieveJob.totalretries() == 0){ //totalretries = 0 then this is the first selection of the request lifeCycleSerDeser.first_selected_time = time(nullptr); @@ -907,6 +925,15 @@ auto RetrieveRequest::asyncUpdateJobOwner(uint32_t copyNumber, const std::string af.deserialize(payload.archivefile()); retRef.m_archiveFile = af; retRef.m_jobStatus = j.status(); + if (payload.has_activity_weight()) { + retRef.m_retrieveActivityDescription = RetrieveActivityDescription{ + payload.activity_weight().priority(), payload.activity_weight().disk_instance_name(), + payload.activity_weight().activity(), payload.activity_weight().creation_time(), + payload.activity_weight().weight(), 0 + }; + } + if (payload.has_disk_system_name()) + retRef.m_diskSystemName = payload.disk_system_name(); RetrieveRequest::updateLifecycleTiming(payload,j); LifecycleTimingsSerDeser lifeCycleSerDeser; lifeCycleSerDeser.deserialize(payload.lifecycle_timings()); @@ -958,6 +985,20 @@ const RetrieveRequest::RepackInfo& RetrieveRequest::AsyncJobOwnerUpdater::getRep return m_repackInfo; } +//------------------------------------------------------------------------------ +// RetrieveRequest::AsyncJobOwnerUpdater::getRetrieveActivityDescription() +//------------------------------------------------------------------------------ +const optional<RetrieveActivityDescription>& RetrieveRequest::AsyncJobOwnerUpdater::getRetrieveActivityDescription() { + return m_retrieveActivityDescription; +} + +//------------------------------------------------------------------------------ +// RetrieveRequest::AsyncJobOwnerUpdater::getDiskSystemName() +//------------------------------------------------------------------------------ +const optional<std::string>& RetrieveRequest::AsyncJobOwnerUpdater::getDiskSystemName() { + return m_diskSystemName; +} + //------------------------------------------------------------------------------ // RetrieveRequest::AsyncJobOwnerUpdater::getRetrieveRequest() //------------------------------------------------------------------------------ diff --git a/objectstore/RetrieveRequest.hpp b/objectstore/RetrieveRequest.hpp index b93d054da669ef4c4d0db9d7309e262acde4fa7c..63c37f74a2efd7b3f7b8d76518723668bba00620 100644 --- a/objectstore/RetrieveRequest.hpp +++ b/objectstore/RetrieveRequest.hpp @@ -225,6 +225,8 @@ public: const common::dataStructures::RetrieveRequest &getRetrieveRequest(); const common::dataStructures::ArchiveFile &getArchiveFile(); const RepackInfo &getRepackInfo(); + const optional<RetrieveActivityDescription> &getRetrieveActivityDescription(); + const optional<std::string> &getDiskSystemName(); private: std::function<std::string(const std::string &)> m_updaterCallback; std::unique_ptr<Backend::AsyncUpdater> m_backendUpdater; @@ -232,6 +234,8 @@ public: common::dataStructures::ArchiveFile m_archiveFile; RepackInfo m_repackInfo; serializers::RetrieveJobStatus m_jobStatus; + optional<RetrieveActivityDescription> m_retrieveActivityDescription; + optional<std::string> m_diskSystemName; }; // An owner updater factory. The owner MUST be previousOwner for the update to be executed. AsyncJobOwnerUpdater *asyncUpdateJobOwner(uint32_t copyNumber, const std::string &owner, const std::string &previousOwner); @@ -243,6 +247,8 @@ public: void setActivityIfNeeded(const cta::common::dataStructures::RetrieveRequest & retrieveRequest, const cta::common::dataStructures::RetrieveFileQueueCriteria& criteria); optional<RetrieveActivityDescription> getActivity(); + void setDiskSystemName(const std::string & diskSystemName); + optional<std::string> getDiskSystemName(); cta::common::dataStructures::RetrieveFileQueueCriteria getRetrieveFileQueueCriteria(); cta::common::dataStructures::ArchiveFile getArchiveFile(); cta::common::dataStructures::EntryLog getEntryLog(); diff --git a/objectstore/Sorter.cpp b/objectstore/Sorter.cpp index 4ea3365f41e3208b4a623f1334b98be22299a986..54b0809cca7cadb23be59a19fb58a675ccd16b3d 100644 --- a/objectstore/Sorter.cpp +++ b/objectstore/Sorter.cpp @@ -159,7 +159,7 @@ void Sorter::executeRetrieveAlgorithm(const std::string vid, std::string& queueA Sorter::RetrieveJob job = std::get<0>(jobToAdd->jobToQueue); succeededJobs[job.jobDump.copyNb] = jobToAdd; previousOwner = job.previousOwner->getAgentAddress(); - jobsToAdd.push_back({job.retrieveRequest.get(),job.jobDump.copyNb,job.fSeq,job.fileSize,job.mountPolicy,job.jobDump.status,job.activityDescription}); + jobsToAdd.push_back({job.retrieveRequest.get(),job.jobDump.copyNb,job.fSeq,job.fileSize,job.mountPolicy,job.jobDump.status,job.activityDescription,job.diskSystemName}); } try{ algo.referenceAndSwitchOwnershipIfNecessary(vid,previousOwner,queueAddress,jobsToAdd,lc); @@ -185,7 +185,7 @@ void Sorter::dispatchRetrieveAlgorithm(const std::string vid, const JobQueueType this->executeRetrieveAlgorithm<RetrieveQueueToReportForUser>(vid,queueAddress,jobs,lc); break; case JobQueueType::JobsToTransferForUser: - this->executeRetrieveAlgorithm<RetrieveQueueToTransferForUser>(vid,queueAddress,jobs,lc); + this->executeRetrieveAlgorithm<RetrieveQueueToTransfer>(vid,queueAddress,jobs,lc); break; case JobQueueType::JobsToReportToRepackForSuccess: this->executeRetrieveAlgorithm<RetrieveQueueToReportToRepackForSuccess>(vid,queueAddress,jobs,lc); @@ -193,8 +193,6 @@ void Sorter::dispatchRetrieveAlgorithm(const std::string vid, const JobQueueType case JobQueueType::JobsToReportToRepackForFailure: this->executeRetrieveAlgorithm<RetrieveQueueToReportToRepackForFailure>(vid,queueAddress,jobs,lc); break; - case JobQueueType::JobsToTransferForRepack: - this->executeRetrieveAlgorithm<RetrieveQueueToTransferForRepack>(vid, queueAddress,jobs,lc); break; case JobQueueType::FailedJobs: break; @@ -216,6 +214,8 @@ Sorter::RetrieveJob Sorter::createRetrieveJob(std::shared_ptr<RetrieveRequest> r jobToAdd.jobDump.status = retrieveRequest->getJobStatus(jobToAdd.jobDump.copyNb); jobToAdd.fileSize = archiveFile.fileSize; jobToAdd.jobQueueType = retrieveRequest->getQueueType(copyNb); //May throw an exception + jobToAdd.activityDescription = retrieveRequest->getActivity(); + jobToAdd.diskSystemName = retrieveRequest->getDiskSystemName(); return jobToAdd; } @@ -308,7 +308,7 @@ std::set<std::string> Sorter::getCandidateVidsToTransfer(RetrieveRequestInfosAcc using serializers::RetrieveJobStatus; std::set<std::string> candidateVids; for(auto& j: requestAccessor.getJobs()){ - if(j.status == RetrieveJobStatus::RJS_ToTransferForUser){ + if(j.status == RetrieveJobStatus::RJS_ToTransfer){ candidateVids.insert(requestAccessor.getArchiveFile().tapeFiles.at(j.copyNb).vid); } } @@ -401,6 +401,8 @@ Sorter::RetrieveJob OStoreRetrieveRequestAccessor::createRetrieveJob(const cta:: ret.jobDump.status = m_retrieveRequest->getJobStatus(ret.jobDump.copyNb); ret.jobQueueType = m_retrieveRequest->getQueueType(copyNb); ret.fileSize = archiveFile.fileSize; + ret.activityDescription = m_retrieveRequest->getActivity(); + ret.diskSystemName = m_retrieveRequest->getDiskSystemName(); return ret; } diff --git a/objectstore/Sorter.hpp b/objectstore/Sorter.hpp index 81dafeb0aaea21dc35be0f98ee0907fd1f764a53..d13967be86d0e7716257d71c1c6bcae4ea50dd91 100644 --- a/objectstore/Sorter.hpp +++ b/objectstore/Sorter.hpp @@ -107,6 +107,7 @@ public: common::dataStructures::MountPolicy mountPolicy; cta::objectstore::JobQueueType jobQueueType; optional<RetrieveActivityDescription> activityDescription; + optional<std::string> diskSystemName; }; /** diff --git a/objectstore/SorterTest.cpp b/objectstore/SorterTest.cpp index 5374d6139e3dc779ea7717735b64e274f0b6ee8f..13f63a0e61f544fd59b40d3dd6ac0bbf94805e43 100644 --- a/objectstore/SorterTest.cpp +++ b/objectstore/SorterTest.cpp @@ -264,7 +264,8 @@ TEST(ObjectStore,SorterInsertRetrieveRequest){ rqc.mountPolicy.retrievePriority = 1; rr.setRetrieveFileQueueCriteria(rqc); - rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransferForRepack); + // Make sure job 1 will get queued by failing the other one. + rr.setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_Failed); cta::common::dataStructures::RetrieveRequest sReq; sReq.archiveFileID = rqc.archiveFile.archiveFileID; @@ -298,7 +299,7 @@ TEST(ObjectStore,SorterInsertRetrieveRequest){ allFutures.clear(); - typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> Algo; + typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> Algo; Algo algo(be,agentRef); typename Algo::PopCriteria criteria; @@ -326,6 +327,10 @@ TEST(ObjectStore,SorterInsertRetrieveRequest){ { ScopedExclusiveLock sel(*retrieveRequest); retrieveRequest->fetch(); + // Make sure now copy 2 will get queued. + retrieveRequest->setJobStatus(1,cta::objectstore::serializers::RetrieveJobStatus::RJS_Failed); + retrieveRequest->setJobStatus(2,cta::objectstore::serializers::RetrieveJobStatus::RJS_ToTransfer); + retrieveRequest->commit(); ASSERT_NO_THROW(sorter.insertRetrieveRequest(retrieveRequest,agentRef,cta::optional<uint32_t>(2),lc)); @@ -345,11 +350,12 @@ TEST(ObjectStore,SorterInsertRetrieveRequest){ } ASSERT_EQ(sorter.getAllRetrieve().size(),0); - typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForRepack> Algo; + typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> Algo; Algo algo(be,agentRef); typename Algo::PopCriteria criteria; criteria.files = 1; + criteria.bytes = 1000; typename Algo::PoppedElementsBatch elements = algo.popNextBatch("Tape1",criteria,lc); ASSERT_EQ(elements.elements.size(),1); auto& elt = elements.elements.front(); @@ -371,10 +377,14 @@ TEST(ObjectStore,SorterInsertRetrieveRequest){ { ScopedExclusiveLock sel(*retrieveRequest); retrieveRequest->fetch(); + // We should be forbidden to force queueing a non-exsiting copy number. ASSERT_THROW(sorter.insertRetrieveRequest(retrieveRequest,agentRef,cta::optional<uint32_t>(4),lc),cta::exception::Exception); retrieveRequest->setJobStatus(1,serializers::RetrieveJobStatus::RJS_ToReportToRepackForSuccess); + retrieveRequest->setJobStatus(2,serializers::RetrieveJobStatus::RJS_Failed); + retrieveRequest->commit(); + // We should be forbidden to requeue a request if no copy is in status ToTranfer. ASSERT_THROW(sorter.insertRetrieveRequest(retrieveRequest,agentRef,cta::nullopt,lc),cta::exception::Exception); sel.release(); @@ -630,7 +640,7 @@ TEST(ObjectStore,SorterInsertDifferentTypesOfRequests){ { //Test the Retrieve Jobs - typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> Algo; + typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> Algo; Algo algo(be,agentRef); typename Algo::PopCriteria criteria; criteria.files = 2; @@ -919,7 +929,7 @@ TEST(ObjectStore,SorterInsertRetrieveRequestNotFetched){ job.fSeq = tf.fSeq; job.fileSize = rqc.archiveFile.fileSize; job.jobDump.copyNb = tf.copyNb; - job.jobDump.status = serializers::RetrieveJobStatus::RJS_ToTransferForUser; + job.jobDump.status = serializers::RetrieveJobStatus::RJS_ToTransfer; job.jobQueueType = JobQueueType::JobsToTransferForUser; job.mountPolicy = rqc.mountPolicy; job.previousOwner = &agentRef; @@ -946,7 +956,7 @@ TEST(ObjectStore,SorterInsertRetrieveRequestNotFetched){ { //Test the Retrieve Jobs - typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> Algo; + typedef ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> Algo; Algo algo(be,agentRef); typename Algo::PopCriteria criteria; criteria.files = 2; diff --git a/objectstore/cta.proto b/objectstore/cta.proto index 1a4272fb7cffc2fb08adef68467cf6c584f9848e..9523face634a1920c4781bc165d36f688880a188 100644 --- a/objectstore/cta.proto +++ b/objectstore/cta.proto @@ -156,6 +156,7 @@ message Agent { required uint64 timeout_us = 2002; repeated string ownedobjects = 2003; optional bool being_garbage_collected = 2004 [default = false]; + optional bool gc_needed = 2005 [default = false]; } message AgentRegister { @@ -210,6 +211,12 @@ message ArchiveFile { // ------------- Drives handling ---------------------------------------------- +message DiskSpaceReservation { + // Each drive keeps tabs of its intended + required string disk_system_name = 5100; + required uint64 reserved_bytes = 5110; +} + message DriveConfig { required string category = 13000; required string key = 13001; @@ -242,7 +249,7 @@ message DriveState { required bool desiredUp = 5019; required bool desiredForceDown = 5020; optional string currentvid = 5021; - optional string cta_version = 5034; + optional string cta_version = 5035; optional uint64 current_priority = 5028; optional string current_activity = 5029; optional double current_activity_weight = 5030; @@ -253,9 +260,10 @@ message DriveState { optional uint64 next_priority = 5031; optional string next_activity = 5032; optional double next_activity_weight = 5033; - optional string dev_file_name = 5035; - optional string raw_library_slot = 5036; - repeated DriveConfig drive_config = 5037; + repeated DiskSpaceReservation disk_space_reservations = 5034; + optional string dev_file_name = 5036; + optional string raw_library_slot = 5037; + repeated DriveConfig drive_config = 5038; // TODO: implement or remove required EntryLog creationlog = 5023; } @@ -367,12 +375,11 @@ message ArchiveRequest { // of the request. enum RetrieveJobStatus { - RJS_ToTransferForUser = 1; + RJS_ToTransfer = 1; RJS_ToReportToUserForFailure = 997; RJS_Failed = 998; RJS_ToReportToRepackForSuccess = 1002; //For Retrieve request created by a Repack request RJS_ToReportToRepackForFailure = 1003; - RJS_ToTransferForRepack = 1004; } message SchedulerRetrieveRequest { @@ -439,6 +446,7 @@ message RetrieveRequest { required bool isrepack = 9157; optional RetrieveRequestRepackInfo repack_info = 9158; optional LifecycleTimings lifecycle_timings = 9159; + optional string disk_system_name = 9161; } message ValueCountPair { @@ -493,6 +501,7 @@ message RetrieveJobPointer { // For activity (if present), we need disk instance and activity name (priority is always provided) optional string disk_instance_name = 3109; optional string activity = 3110; + optional string destination_disk_system_name = 3111; } message RetrieveQueueShardPointer { @@ -525,6 +534,9 @@ message RetrieveQueue { required uint64 oldestjobcreationtime = 10150; required uint64 mapsrebuildcount = 10160; required uint64 maxshardsize = 10170; + optional uint64 sleep_for_free_space_since = 10180; + optional string disk_system_slept_for = 10190; + optional uint64 sleep_time = 10200; } // ------------- Repack data strcutures ---------------------------------------- diff --git a/scheduler/OStoreDB/MemQueues.cpp b/scheduler/OStoreDB/MemQueues.cpp index 7a1cafd3a8660db7dbc335a8894c1280838eb7b6..67c3172e1ae5d59710eb6687705b4483047cb17b 100644 --- a/scheduler/OStoreDB/MemQueues.cpp +++ b/scheduler/OStoreDB/MemQueues.cpp @@ -55,7 +55,7 @@ void MemQueue<objectstore::RetrieveRequest, objectstore::RetrieveQueue>::special if (j.copyNb == job.copyNb) { auto criteria = request.getRetrieveFileQueueCriteria(); jtal.push_back({j.copyNb, j.fSeq, request.getAddressIfSet(), criteria.archiveFile.fileSize, - criteria.mountPolicy, request.getEntryLog().time, request.getActivity()}); + criteria.mountPolicy, request.getEntryLog().time, request.getActivity(), request.getDiskSystemName()}); request.setActiveCopyNumber(j.copyNb); request.setOwner(queueAddress); goto jobAdded; diff --git a/scheduler/OStoreDB/OStoreDB.cpp b/scheduler/OStoreDB/OStoreDB.cpp index 5500f1eee6d350a1a57c56a8ba77356fd33cd75e..3aebfc90220b445f0735ad62567b02b6a375cac8 100644 --- a/scheduler/OStoreDB/OStoreDB.cpp +++ b/scheduler/OStoreDB/OStoreDB.cpp @@ -314,12 +314,12 @@ void OStoreDB::fetchMountInfo(SchedulerDatabase::TapeMountDecisionInfo& tmdi, Ro auto & m = tmdi.potentialMounts.back(); m.vid = rqp.vid; m.type = cta::common::dataStructures::MountType::Retrieve; - m.bytesQueued = rqueue.getJobsSummary().bytes; - m.filesQueued = rqueue.getJobsSummary().jobs; + m.bytesQueued = rqSummary.bytes; + m.filesQueued = rqSummary.jobs; m.oldestJobStartTime = rqueue.getJobsSummary().oldestJobStartTime; - m.priority = rqueue.getJobsSummary().priority; - m.maxDrivesAllowed = rqueue.getJobsSummary().maxDrivesAllowed; - m.minRequestAge = rqueue.getJobsSummary().minRetrieveRequestAge; + m.priority = rqSummary.priority; + m.maxDrivesAllowed = rqSummary.maxDrivesAllowed; + m.minRequestAge = rqSummary.minRetrieveRequestAge; m.logicalLibrary = ""; // The logical library is not known here, and will be determined by the caller. m.tapePool = ""; // The tape pool is not know and will be determined by the caller. m.vendor = ""; // The vendor is not known here, and will be determined by the caller. @@ -331,6 +331,15 @@ void OStoreDB::fetchMountInfo(SchedulerDatabase::TapeMountDecisionInfo& tmdi, Ro m.activityNameAndWeightedMountCount.value().weight = ac.weight; m.activityNameAndWeightedMountCount.value().weightedMountCount = 0.0; // This value will be computed later by the caller. m.activityNameAndWeightedMountCount.value().mountCount = 0; // This value will be computed later by the caller. + // We will display the sleep flag only if it is not expired (15 minutes timeout, hardcoded). + // This allows having a single decision point instead of implementing is at the consumer levels. + if (rqSummary.sleepInfo && (::time(nullptr) < (rqSummary.sleepInfo.value().sleepStartTime + + (int64_t) rqSummary.sleepInfo.value().sleepTime)) ) { + m.sleepingMount = true; + m.sleepStartTime = rqSummary.sleepInfo.value().sleepStartTime; + m.diskSystemSleptFor = rqSummary.sleepInfo.value().diskSystemSleptFor; + m.sleepTime = rqSummary.sleepInfo.value().sleepTime; + } } } if (jobsWithoutActivity) { @@ -338,18 +347,27 @@ void OStoreDB::fetchMountInfo(SchedulerDatabase::TapeMountDecisionInfo& tmdi, Ro auto & m = tmdi.potentialMounts.back(); m.vid = rqp.vid; m.type = cta::common::dataStructures::MountType::Retrieve; - m.bytesQueued = rqueue.getJobsSummary().bytes; - m.filesQueued = rqueue.getJobsSummary().jobs; - m.oldestJobStartTime = rqueue.getJobsSummary().oldestJobStartTime; - m.priority = rqueue.getJobsSummary().priority; - m.maxDrivesAllowed = rqueue.getJobsSummary().maxDrivesAllowed; - m.minRequestAge = rqueue.getJobsSummary().minRetrieveRequestAge; + m.bytesQueued = rqSummary.bytes; + m.filesQueued = rqSummary.jobs; + m.oldestJobStartTime = rqSummary.oldestJobStartTime; + m.priority = rqSummary.priority; + m.maxDrivesAllowed = rqSummary.maxDrivesAllowed; + m.minRequestAge = rqSummary.minRetrieveRequestAge; m.logicalLibrary = ""; // The logical library is not known here, and will be determined by the caller. m.tapePool = ""; // The tape pool is not know and will be determined by the caller. m.vendor = ""; // The vendor is not known here, and will be determined by the caller. m.mediaType = ""; // The logical library is not known here, and will be determined by the caller. m.vo = ""; // The vo is not known here, and will be determined by the caller. m.capacityInBytes = 0; // The capacity is not known here, and will be determined by the caller. + // We will display the sleep flag only if it is not expired (15 minutes timeout, hardcoded). + // This allows having a single decision point instead of implementing is at the consumer levels. + if (rqSummary.sleepInfo && (::time(nullptr) < (rqSummary.sleepInfo.value().sleepStartTime + + (int64_t) rqSummary.sleepInfo.value().sleepTime)) ) { + m.sleepingMount = true; + m.sleepStartTime = rqSummary.sleepInfo.value().sleepStartTime; + m.diskSystemSleptFor = rqSummary.sleepInfo.value().diskSystemSleptFor; + rqSummary.sleepInfo.value().sleepTime; + } } } else { tmdi.queueTrimRequired = true; @@ -1102,7 +1120,7 @@ void OStoreDB::setRetrieveJobBatchReportedToUser(std::list<cta::SchedulerDatabas insertedElements.emplace_back(CaRQF::InsertedElement{ &j.job->m_retrieveRequest, tf_it->copyNb, tf_it->fSeq, j.job->archiveFile.fileSize, common::dataStructures::MountPolicy(), serializers::RetrieveJobStatus::RJS_Failed, - j.job->m_activityDescription + j.job->m_activityDescription, j.job->m_diskSystemName }); } try { @@ -1129,7 +1147,8 @@ std::list<SchedulerDatabase::RetrieveQueueStatistics> OStoreDB::getRetrieveQueue // OStoreDB::queueRetrieve() //------------------------------------------------------------------------------ SchedulerDatabase::RetrieveRequestInfo OStoreDB::queueRetrieve(cta::common::dataStructures::RetrieveRequest& rqst, - const cta::common::dataStructures::RetrieveFileQueueCriteria& criteria, log::LogContext &logContext) { + const cta::common::dataStructures::RetrieveFileQueueCriteria& criteria, const optional<std::string> diskSystemName, + log::LogContext &logContext) { assertAgentAddressSet(); auto mutexForHelgrind = cta::make_unique<cta::threading::Mutex>(); cta::threading::MutexLocker mlForHelgrind(*mutexForHelgrind); @@ -1183,6 +1202,7 @@ SchedulerDatabase::RetrieveRequestInfo OStoreDB::queueRetrieve(cta::common::data rReq->setRetrieveFileQueueCriteria(criteria); rReq->setActivityIfNeeded(rqst, criteria); rReq->setCreationTime(rqst.creationLog.time); + if (diskSystemName) rReq->setDiskSystemName(diskSystemName.value()); // Find the job corresponding to the vid (and check we indeed have one). auto jobs = rReq->getJobs(); objectstore::RetrieveRequest::JobDump job; @@ -2233,7 +2253,10 @@ void OStoreDB::RepackRequest::setLastExpandedFSeq(uint64_t fseq){ //------------------------------------------------------------------------------ // OStoreDB::RepackRequest::addSubrequests() //------------------------------------------------------------------------------ -uint64_t OStoreDB::RepackRequest::addSubrequestsAndUpdateStats(std::list<Subrequest>& repackSubrequests, cta::common::dataStructures::ArchiveRoute::FullMap& archiveRoutesMap, uint64_t maxFSeqLowBound, const uint64_t maxAddedFSeq, const cta::SchedulerDatabase::RepackRequest::TotalStatsFiles &totalStatsFiles, log::LogContext& lc) { +uint64_t OStoreDB::RepackRequest::addSubrequestsAndUpdateStats(std::list<Subrequest>& repackSubrequests, + cta::common::dataStructures::ArchiveRoute::FullMap& archiveRoutesMap, uint64_t maxFSeqLowBound, + const uint64_t maxAddedFSeq, const cta::SchedulerDatabase::RepackRequest::TotalStatsFiles &totalStatsFiles, + disk::DiskSystemList diskSystemList, log::LogContext& lc) { // We need to prepare retrieve requests names and reference them, create them, enqueue them. uint64_t nbRetrieveSubrequestsCreated = 0; objectstore::ScopedExclusiveLock rrl (m_repackRequest); @@ -2280,6 +2303,11 @@ uint64_t OStoreDB::RepackRequest::addSubrequestsAndUpdateStats(std::list<Subrequ // dsrr.errorReportURL: We leave this bank as the reporting will be done to the repack request, // stored in the repack info. rr->setSchedulerRequest(schedReq); + // Add the disk system information if needed. + try { + auto dsName = diskSystemList.getDSNAme(schedReq.dstURL); + rr->setDiskSystemName(dsName); + } catch (std::out_of_range &) {} // Set the repack info. RetrieveRequest::RepackInfo rRRepackInfo; try { @@ -2459,11 +2487,12 @@ uint64_t OStoreDB::RepackRequest::addSubrequestsAndUpdateStats(std::list<Subrequ lc.log(log::ERR, "In OStoreDB::RepackRequest::addSubRequests(), reported the failed creation of Retrieve Requests to the Repack request"); } // We now have created the subrequests. Time to enqueue. + // TODO: the lock/fetch could be parallelized { objectstore::Sorter sorter(*m_oStoreDB.m_agentReference, m_oStoreDB.m_objectStore, m_oStoreDB.m_catalogue); - std::list<std::unique_ptr<objectstore::ScopedExclusiveLock>> locks; + std::list<objectstore::ScopedExclusiveLock> locks; for (auto &is: asyncInsertedSubrequestInfoList) { - locks.push_back(cta::make_unique<objectstore::ScopedExclusiveLock>(*is.request)); + locks.emplace_back(*is.request); is.request->fetch(); sorter.insertRetrieveRequest(is.request, *m_oStoreDB.m_agentReference, is.activeCopyNb, lc); } @@ -2471,7 +2500,6 @@ uint64_t OStoreDB::RepackRequest::addSubrequestsAndUpdateStats(std::list<Subrequ locks.clear(); sorter.flushAll(lc); } - m_repackRequest.setLastExpandedFSeq(fSeq); m_repackRequest.commit(); return nbRetrieveSubrequestsCreated; @@ -2500,6 +2528,9 @@ void OStoreDB::RepackRequest::fail() { m_repackRequest.commit(); } +//------------------------------------------------------------------------------ +// OStoreDB::RepackRequest::requeueInToExpandQueue() +//------------------------------------------------------------------------------ void OStoreDB::RepackRequest::requeueInToExpandQueue(log::LogContext& lc){ ScopedExclusiveLock rrl(m_repackRequest); m_repackRequest.fetch(); @@ -2516,6 +2547,9 @@ void OStoreDB::RepackRequest::requeueInToExpandQueue(log::LogContext& lc){ rqteAlgo.referenceAndSwitchOwnership(nullopt, previousOwner, insertedElements, lc); } +//------------------------------------------------------------------------------ +// OStoreDB::RepackRequest::setExpandStartedAndChangeStatus() +//------------------------------------------------------------------------------ void OStoreDB::RepackRequest::setExpandStartedAndChangeStatus(){ ScopedExclusiveLock rrl(m_repackRequest); m_repackRequest.fetch(); @@ -2524,6 +2558,9 @@ void OStoreDB::RepackRequest::setExpandStartedAndChangeStatus(){ m_repackRequest.commit(); } +//------------------------------------------------------------------------------ +// OStoreDB::RepackRequest::fillLastExpandedFSeqAndTotalStatsFile() +//------------------------------------------------------------------------------ void OStoreDB::RepackRequest::fillLastExpandedFSeqAndTotalStatsFile(uint64_t& fSeq, TotalStatsFiles& totalStatsFiles) { ScopedExclusiveLock rrl(m_repackRequest); m_repackRequest.fetch(); @@ -2851,6 +2888,19 @@ void OStoreDB::updateDriveStatus(const common::dataStructures::DriveInfo& driveI throw exception::Exception("Unexpected status in DriveRegister::reportDriveStatus"); } ds.setState(driveState); + // If the drive is a state incompatible with space reservation, make sure there is none: + switch (inputs.status) { + case DriveStatus::CleaningUp: + case DriveStatus::Down: + case DriveStatus::Shutdown: + case DriveStatus::Unknown: + case DriveStatus::Unloading: + case DriveStatus::Unmounting: + case DriveStatus::Up: + ds.resetDiskSpaceReservation(); + default: + break; + } ds.commit(); } @@ -3553,19 +3603,82 @@ const OStoreDB::RetrieveMount::MountInfo& OStoreDB::RetrieveMount::getMountInfo( //------------------------------------------------------------------------------ // OStoreDB::RetrieveMount::getNextJobBatch() //------------------------------------------------------------------------------ -std::list<std::unique_ptr<SchedulerDatabase::RetrieveJob>> OStoreDB::RetrieveMount:: -getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, log::LogContext &logContext) -{ - typedef objectstore::ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> RQAlgos; +std::list<std::unique_ptr<SchedulerDatabase::RetrieveJob>> OStoreDB::RetrieveMount::getNextJobBatch(uint64_t filesRequested, + uint64_t bytesRequested, cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, log::LogContext& logContext) { + // Pop a batch of files to retrieve and, for the ones having a documented disk system name, reserve the space + // that they will require. In case we cannot allocate the space for some of them, mark the destination filesystem as + // full and stop popping from it, after requeueing the jobs. + bool failedAllocation = false; + SchedulerDatabase::DiskSpaceReservationRequest diskSpaceReservationRequest; + typedef objectstore::ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> RQAlgos; RQAlgos rqAlgos(m_oStoreDB.m_objectStore, *m_oStoreDB.m_agentReference); - RQAlgos::PopCriteria popCriteria(filesRequested, bytesRequested); - auto jobs = rqAlgos.popNextBatch(mountInfo.vid, popCriteria, logContext); - // We can construct the return value + RQAlgos::PoppedElementsBatch jobs; + retryPop: + { + RQAlgos::PopCriteria popCriteria(filesRequested, bytesRequested); + for (auto &dsts: m_diskSystemsToSkip) popCriteria.diskSystemsToSkip.insert({dsts.name, dsts.sleepTime}); + jobs = rqAlgos.popNextBatch(mountInfo.vid, popCriteria, logContext); + // Try and allocate data for the popped jobs. + // Compute the necessary space in each targeted disk system. + std::map<std::string, uint64_t> spaceMap; + for (auto &j: jobs.elements) + if (j.diskSystemName) + diskSpaceReservationRequest.addRequest(j.diskSystemName.value(), j.archiveFile.fileSize); + // Get the existing reservation map from drives (including this drive's previous pending reservations). + auto previousDrivesReservations = getExistingDrivesReservations(); + typedef std::pair<std::string, uint64_t> Res; + uint64_t previousDrivesReservationTotal = 0; + previousDrivesReservationTotal = std::accumulate(previousDrivesReservations.begin(), previousDrivesReservations.end(), + previousDrivesReservationTotal, [](uint64_t t, Res a){ return t+a.second;}); + // Get the free space from disk systems involved. + std::set<std::string> diskSystemNames; + for (auto const & dsrr: diskSpaceReservationRequest) diskSystemNames.insert(dsrr.first); + try { + diskSystemFreeSpace.fetchDiskSystemFreeSpace(diskSystemNames, logContext); + } catch (std::exception &ex) { + // Leave a log message before letting the possible exception go up the stack. + log::ScopedParamContainer params(logContext); + params.add("exceptionWhat", ex.what()); + logContext.log(log::ERR, "In OStoreDB::RetrieveMount::getNextJobBatch(): got an exception from diskSystemFreeSpace.fetchDiskSystemFreeSpace()."); + throw; + } + // If any file system does not have enough space, mark it as full for this mount, requeue all (slight but rare inefficiency) + // and retry the pop. + for (auto const & ds: diskSystemNames) { + if (diskSystemFreeSpace.at(ds).freeSpace < diskSpaceReservationRequest.at(ds) + diskSystemFreeSpace.at(ds).targetedFreeSpace + + previousDrivesReservationTotal) { + m_diskSystemsToSkip.insert({ds, diskSystemFreeSpace.getDiskSystemList().at(ds).sleepTime}); + failedAllocation = true; + log::ScopedParamContainer params(logContext); + params.add("diskSystemName", ds) + .add("freeSpace", diskSystemFreeSpace.at(ds).freeSpace) + .add("existingReservations", previousDrivesReservationTotal) + .add("spaceToReserve", diskSpaceReservationRequest.at(ds)) + .add("targetedFreeSpace", diskSystemFreeSpace.at(ds).targetedFreeSpace); + logContext.log(log::WARNING, "In OStoreDB::RetrieveMount::getNextJobBatch(): could not allocate disk space for job batch."); + } + } + } + if (failedAllocation) { + std::list<std::unique_ptr<OStoreDB::RetrieveJob>> rjl; + for (auto & jle: jobs.elements) rjl.emplace_back(new OStoreDB::RetrieveJob(jle.retrieveRequest->getAddressIfSet(), m_oStoreDB, this)); + requeueJobBatch(rjl, logContext); + rjl.clear(); + // Clean up for the next round of popping + jobs.summary.files=0; + jobs.elements.clear(); + failedAllocation = false; + diskSpaceReservationRequest.clear(); + goto retryPop; + } + this->reserveDiskSpace(diskSpaceReservationRequest, logContext); + // Allocation went fine, we can construct the return value (we did not hit any full disk system. std::list<std::unique_ptr<SchedulerDatabase::RetrieveJob>> ret; for(auto &j : jobs.elements) { std::unique_ptr<OStoreDB::RetrieveJob> rj(new OStoreDB::RetrieveJob(j.retrieveRequest->getAddressIfSet(), m_oStoreDB, this)); rj->archiveFile = j.archiveFile; + rj->diskSystemName = j.diskSystemName; rj->retrieveRequest = j.rr; rj->selectedCopyNb = j.copyNb; rj->isRepack = j.repackInfo.isRepack; @@ -3577,6 +3690,85 @@ getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, log::LogContex return ret; } +//------------------------------------------------------------------------------ +// OStoreDB::RetrieveMount::requeueJobBatch() +//------------------------------------------------------------------------------ +void OStoreDB::RetrieveMount::requeueJobBatch(std::list<std::unique_ptr<OStoreDB::RetrieveJob> >& jobBatch, + log::LogContext& logContext) { + objectstore::Sorter sorter(*m_oStoreDB.m_agentReference, m_oStoreDB.m_objectStore, m_oStoreDB.m_catalogue); + std::list<std::shared_ptr<objectstore::RetrieveRequest>> rrlist; + std::list<objectstore::ScopedExclusiveLock> locks; + for (auto & j: jobBatch) { + auto rr = std::make_shared<objectstore::RetrieveRequest>(j->m_retrieveRequest.getAddressIfSet(), m_oStoreDB.m_objectStore); + rrlist.push_back(rr); + locks.emplace_back(*rr); + rr->fetch(); + sorter.insertRetrieveRequest(rr, *m_oStoreDB.m_agentReference, nullopt, logContext); + } + locks.clear(); + rrlist.clear(); + sorter.flushAll(logContext); +} + +//------------------------------------------------------------------------------ +// OStoreDB::RetrieveMount::getExistingDrivesReservations() +//------------------------------------------------------------------------------ +std::map<std::string, uint64_t> OStoreDB::RetrieveMount::getExistingDrivesReservations() { + objectstore::RootEntry re(m_oStoreDB.m_objectStore); + re.fetchNoLock(); + objectstore::DriveRegister dr(re.getDriveRegisterAddress(), m_oStoreDB.m_objectStore); + dr.fetchNoLock(); + auto driveAddresses = dr.getDriveAddresses(); + std::list <objectstore::DriveState> dsList; + std::list <std::unique_ptr<objectstore::DriveState::AsyncLockfreeFetcher>> dsFetchers; + for (auto &d: driveAddresses) { + dsList.emplace_back(d.driveStateAddress, m_oStoreDB.m_objectStore); + dsFetchers.emplace_back(dsList.back().asyncLockfreeFetch()); + } + auto dsf = dsFetchers.begin(); + std::map<std::string, uint64_t> ret; + for (auto &d: dsList) { + try { + (*dsf)->wait(); + dsf++; + for (auto &dsr: d.getDiskSpaceReservations()) { + try { + ret.at(dsr.first) += dsr.second; + } catch (std::out_of_range &) { + ret[dsr.first] = dsr.second; + } + } + } catch (objectstore::Backend::NoSuchObject) { + // If the drive status is not there, we just skip it. + dsf++; + } + } + return ret; +} + +//------------------------------------------------------------------------------ +// OStoreDB::RetrieveMount::reserveDiskSpace() +//------------------------------------------------------------------------------ +void OStoreDB::RetrieveMount::reserveDiskSpace(const DiskSpaceReservationRequest& diskSpaceReservation, log::LogContext & lc) { + // Try add our reservation to the drive status. + objectstore::DriveState ds(m_oStoreDB.m_objectStore); + objectstore::ScopedExclusiveLock dsl; + Helpers::getLockedAndFetchedDriveState(ds, dsl, *m_oStoreDB.m_agentReference, mountInfo.drive, lc, Helpers::CreateIfNeeded::doNotCreate); + for (auto const & dsr: diskSpaceReservation) ds.addDiskSpaceReservation(dsr.first, dsr.second); + ds.commit(); +} + +//------------------------------------------------------------------------------ +// OStoreDB::RetrieveMount::releaseDiskSpace() +//------------------------------------------------------------------------------ +void OStoreDB::RetrieveMount::releaseDiskSpace(const DiskSpaceReservationRequest& diskSpaceReservation, log::LogContext & lc) { + // Try add our reservation to the drive status. + objectstore::DriveState ds(m_oStoreDB.m_objectStore); + objectstore::ScopedExclusiveLock dsl; + Helpers::getLockedAndFetchedDriveState(ds, dsl, *m_oStoreDB.m_agentReference, mountInfo.drive, lc, Helpers::CreateIfNeeded::doNotCreate); + for (auto const & dsr: diskSpaceReservation) ds.substractDiskSpaceReservation(dsr.first, dsr.second); + ds.commit(); +} //------------------------------------------------------------------------------ // OStoreDB::RetrieveMount::complete() //------------------------------------------------------------------------------ @@ -3668,10 +3860,12 @@ void OStoreDB::RetrieveMount::flushAsyncSuccessReports(std::list<cta::SchedulerD std::map<std::string, std::list<OStoreDB::RetrieveJob*>> jobsToRequeueForRepackMap; // We will wait on the asynchronously started reports of jobs, queue the retrieve jobs // for report and remove them from ownership. + SchedulerDatabase::DiskSpaceReservationRequest diskSpaceReservationRequest; // 1) Check the async update result. common::dataStructures::MountPolicy mountPolicy; for (auto & sDBJob: jobsBatch) { auto osdbJob = castFromSchedDBJob(sDBJob); + if (osdbJob->diskSystemName) diskSpaceReservationRequest.addRequest(osdbJob->diskSystemName.value(), osdbJob->archiveFile.fileSize); if (osdbJob->isRepack) { try { osdbJob->m_jobSucceedForRepackReporter->wait(); @@ -3730,6 +3924,7 @@ void OStoreDB::RetrieveMount::flushAsyncSuccessReports(std::list<cta::SchedulerD } } } + releaseDiskSpace(diskSpaceReservationRequest, lc); // 2) Queue the retrieve requests for repack. for (auto & repackRequestQueue: jobsToRequeueForRepackMap) { typedef objectstore::ContainerAlgorithms<RetrieveQueue,RetrieveQueueToReportToRepackForSuccess> RQTRTRFSAlgo; @@ -3740,7 +3935,7 @@ void OStoreDB::RetrieveMount::flushAsyncSuccessReports(std::list<cta::SchedulerD insertedRequests.push_back(RQTRTRFSAlgo::InsertedElement{&req->m_retrieveRequest, req->selectedCopyNb, req->archiveFile.tapeFiles.at(req->selectedCopyNb).fSeq, req->archiveFile.fileSize, mountPolicy, - serializers::RetrieveJobStatus::RJS_ToReportToRepackForSuccess, req->m_activityDescription}); + serializers::RetrieveJobStatus::RJS_ToReportToRepackForSuccess, req->m_activityDescription, req->m_diskSystemName}); requestToJobMap[&req->m_retrieveRequest] = req; } RQTRTRFSAlgo rQTRTRFSAlgo(m_oStoreDB.m_objectStore, *m_oStoreDB.m_agentReference); @@ -4552,6 +4747,13 @@ void OStoreDB::RetrieveJob::failTransfer(const std::string &failureReason, log:: if (!m_jobOwned) throw JobNotOwned("In OStoreDB::RetrieveJob::failTransfer: cannot fail a job not owned"); + // Remove the space reservation for this job as we are done with it (if needed). + if (diskSystemName) { + SchedulerDatabase::DiskSpaceReservationRequest dsrr; + dsrr.addRequest(diskSystemName.value(), archiveFile.fileSize); + m_retrieveMount->releaseDiskSpace(dsrr, lc); + } + // Lock the retrieve request. Fail the job. objectstore::ScopedExclusiveLock rel(m_retrieveRequest); m_retrieveRequest.fetch(); @@ -4637,7 +4839,7 @@ void OStoreDB::RetrieveJob::failTransfer(const std::string &failureReason, log:: CaRqtr::InsertedElement::list insertedElements; insertedElements.push_back(CaRqtr::InsertedElement{ &m_retrieveRequest, tf.copyNb, tf.fSeq, af.fileSize, rfqc.mountPolicy, - serializers::RetrieveJobStatus::RJS_Failed, m_activityDescription + serializers::RetrieveJobStatus::RJS_Failed, m_activityDescription, m_diskSystemName }); m_retrieveRequest.commit(); rel.release(); @@ -4667,7 +4869,7 @@ void OStoreDB::RetrieveJob::failTransfer(const std::string &failureReason, log:: } case NextStep::EnqueueForTransferForUser: { - typedef objectstore::ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransferForUser> CaRqtr; + typedef objectstore::ContainerAlgorithms<RetrieveQueue,RetrieveQueueToTransfer> CaRqtr; // Algorithms suppose the objects are not locked auto retryStatus = m_retrieveRequest.getRetryStatus(selectedCopyNb); @@ -4676,7 +4878,7 @@ void OStoreDB::RetrieveJob::failTransfer(const std::string &failureReason, log:: std::set<std::string> candidateVids; for(auto &tf : af.tapeFiles) { - if(m_retrieveRequest.getJobStatus(tf.copyNb) == serializers::RetrieveJobStatus::RJS_ToTransferForUser) + if(m_retrieveRequest.getJobStatus(tf.copyNb) == serializers::RetrieveJobStatus::RJS_ToTransfer) candidateVids.insert(tf.vid); } if(candidateVids.empty()) { @@ -4704,8 +4906,8 @@ void OStoreDB::RetrieveJob::failTransfer(const std::string &failureReason, log:: CaRqtr::InsertedElement::list insertedElements; insertedElements.push_back(CaRqtr::InsertedElement{ - &m_retrieveRequest, tf.copyNb, tf.fSeq, af.fileSize, rfqc.mountPolicy, serializers::RetrieveJobStatus::RJS_ToTransferForUser, - m_activityDescription + &m_retrieveRequest, tf.copyNb, tf.fSeq, af.fileSize, rfqc.mountPolicy, serializers::RetrieveJobStatus::RJS_ToTransfer, + m_activityDescription, m_diskSystemName }); CaRqtr caRqtr(m_oStoreDB.m_objectStore, *m_oStoreDB.m_agentReference); @@ -4769,7 +4971,7 @@ void OStoreDB::RetrieveJob::failReport(const std::string &failureReason, log::Lo CaRqtr::InsertedElement::list insertedElements; insertedElements.push_back(CaRqtr::InsertedElement{ &m_retrieveRequest, tf.copyNb, tf.fSeq, af.fileSize, rfqc.mountPolicy, - serializers::RetrieveJobStatus::RJS_ToReportToUserForFailure, m_activityDescription + serializers::RetrieveJobStatus::RJS_ToReportToUserForFailure, m_activityDescription, m_diskSystemName }); caRqtr.referenceAndSwitchOwnership(tf.vid, insertedElements, lc); log::ScopedParamContainer params(lc); @@ -4788,7 +4990,7 @@ void OStoreDB::RetrieveJob::failReport(const std::string &failureReason, log::Lo CaRqtr::InsertedElement::list insertedElements; insertedElements.push_back(CaRqtr::InsertedElement{ &m_retrieveRequest, tf.copyNb, tf.fSeq, af.fileSize, rfqc.mountPolicy, - serializers::RetrieveJobStatus::RJS_Failed, m_activityDescription + serializers::RetrieveJobStatus::RJS_Failed, m_activityDescription, m_diskSystemName }); caRqtr.referenceAndSwitchOwnership(tf.vid, insertedElements, lc); log::ScopedParamContainer params(lc); diff --git a/scheduler/OStoreDB/OStoreDB.hpp b/scheduler/OStoreDB/OStoreDB.hpp index fde33e1ddd93376e7a7092329c51f60a3d9add81..6ec6e4a37e4c203c628135ed2c753397a37b6f2b 100644 --- a/scheduler/OStoreDB/OStoreDB.hpp +++ b/scheduler/OStoreDB/OStoreDB.hpp @@ -217,7 +217,22 @@ public: OStoreDB & m_oStoreDB; public: const MountInfo & getMountInfo() override; - std::list<std::unique_ptr<SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, log::LogContext& logContext) override; + std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, + cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, log::LogContext& logContext) override; + private: + void requeueJobBatch(std::list<std::unique_ptr<OStoreDB::RetrieveJob> >& jobBatch, + log::LogContext& logContext); + std::map<std::string, uint64_t> getExistingDrivesReservations(); + void reserveDiskSpace(const DiskSpaceReservationRequest& diskSpaceReservation, log::LogContext & lc); + struct DiskSystemToSkip { + std::string name; + uint64_t sleepTime; + bool operator<(const DiskSystemToSkip & o) const { return name < o.name; } + }; + std::set<DiskSystemToSkip> m_diskSystemsToSkip; + public: + /// Public but non overriding function used by retrieve jobs (on failure to transfer): + void releaseDiskSpace(const DiskSpaceReservationRequest& diskSpaceReservation, log::LogContext & lc); void complete(time_t completionTime) override; void setDriveStatus(cta::common::dataStructures::DriveStatus status, time_t completionTime) override; void setTapeSessionStats(const castor::tape::tapeserver::daemon::TapeSessionStats &stats) override; @@ -258,6 +273,7 @@ public: std::unique_ptr<objectstore::RetrieveRequest::AsyncJobSucceedForRepackReporter> m_jobSucceedForRepackReporter; objectstore::RetrieveRequest::RepackInfo m_repackInfo; optional<objectstore::RetrieveActivityDescription> m_activityDescription; + optional<std::string> m_diskSystemName; }; static RetrieveJob * castFromSchedDBJob(SchedulerDatabase::RetrieveJob * job); @@ -297,11 +313,11 @@ public: CTA_GENERATE_EXCEPTION_CLASS(RetrieveRequestHasNoCopies); CTA_GENERATE_EXCEPTION_CLASS(TapeCopyNumberOutOfRange); SchedulerDatabase::RetrieveRequestInfo queueRetrieve(cta::common::dataStructures::RetrieveRequest& rqst, - const cta::common::dataStructures::RetrieveFileQueueCriteria &criteria, log::LogContext &logContext) override; + const cta::common::dataStructures::RetrieveFileQueueCriteria &criteria, const optional<std::string> diskSystemName, + log::LogContext &logContext) override; void cancelRetrieve(const std::string& instanceName, const cta::common::dataStructures::CancelRetrieveRequest& rqst, log::LogContext& lc) override; - std::list<RetrieveRequestDump> getRetrieveRequestsByVid(const std::string& vid) const override; std::list<RetrieveRequestDump> getRetrieveRequestsByRequester(const std::string& vid) const override; @@ -348,7 +364,8 @@ public: RepackRequest(const std::string &jobAddress, OStoreDB &oStoreDB) : m_oStoreDB(oStoreDB), m_repackRequest(jobAddress, m_oStoreDB.m_objectStore){} uint64_t addSubrequestsAndUpdateStats(std::list<Subrequest>& repackSubrequests, cta::common::dataStructures::ArchiveRoute::FullMap& archiveRoutesMap, - uint64_t maxFSeqLowBound, const uint64_t maxAddedFSeq, const TotalStatsFiles &totalStatsFiles, log::LogContext& lc) override; + uint64_t maxFSeqLowBound, const uint64_t maxAddedFSeq, const TotalStatsFiles &totalStatsFiles, disk::DiskSystemList diskSystemList, + log::LogContext& lc) override; void expandDone() override; void fail() override; void requeueInToExpandQueue(log::LogContext& lc) override; diff --git a/scheduler/OStoreDB/OStoreDBFactory.hpp b/scheduler/OStoreDB/OStoreDBFactory.hpp index 9802f13056575b38569a7ca9b7e253eacc1520fc..7d7b8b0d00f811cfb508c941302743cb2711cde3 100644 --- a/scheduler/OStoreDB/OStoreDBFactory.hpp +++ b/scheduler/OStoreDB/OStoreDBFactory.hpp @@ -219,8 +219,9 @@ public: } SchedulerDatabase::RetrieveRequestInfo queueRetrieve(common::dataStructures::RetrieveRequest& rqst, - const common::dataStructures::RetrieveFileQueueCriteria &criteria, log::LogContext &logContext) override { - return m_OStoreDB.queueRetrieve(rqst, criteria, logContext); + const common::dataStructures::RetrieveFileQueueCriteria &criteria, const optional<std::string> diskSystemName, + log::LogContext &logContext) override { + return m_OStoreDB.queueRetrieve(rqst, criteria, diskSystemName, logContext); } void cancelRetrieve(const std::string& instanceName, const cta::common::dataStructures::CancelRetrieveRequest& rqst, diff --git a/scheduler/OStoreDB/OStoreDBTest.cpp b/scheduler/OStoreDB/OStoreDBTest.cpp index 042245277ee7c79d2326cdbe81112320e9159163..4954c16b2dc4f205feb7a1b6c678e4a82c90e3de 100644 --- a/scheduler/OStoreDB/OStoreDBTest.cpp +++ b/scheduler/OStoreDB/OStoreDBTest.cpp @@ -223,7 +223,7 @@ TEST_P(OStoreDBTest, MemQueuesSharedAddToArchiveQueue) { }); jobInsertions.emplace_back(std::async(std::launch::async ,lambdas.back())); } - for (auto &j: jobInsertions) { j.wait(); } + for (auto &j: jobInsertions) { j.get(); } jobInsertions.clear(); lambdas.clear(); diff --git a/scheduler/RetrieveMount.cpp b/scheduler/RetrieveMount.cpp index 470f7412c6ea2d535ea71df9fab2d6ed05f8d704..6ed0f8f5ec0356e2fa900057d1b840fda2496e9e 100644 --- a/scheduler/RetrieveMount.cpp +++ b/scheduler/RetrieveMount.cpp @@ -19,6 +19,7 @@ #include "scheduler/RetrieveMount.hpp" #include "common/Timer.hpp" #include "common/log/TimingList.hpp" +#include "disk/DiskSystem.hpp" //------------------------------------------------------------------------------ // constructor @@ -111,7 +112,7 @@ std::string cta::RetrieveMount::getMediaType() const } //------------------------------------------------------------------------------ -// getVo() +// getVendor() //------------------------------------------------------------------------------ std::string cta::RetrieveMount::getVendor() const { @@ -122,6 +123,9 @@ std::string cta::RetrieveMount::getVendor() const return sVendor.str(); } +//------------------------------------------------------------------------------ +// getCapacityInBytes() +//------------------------------------------------------------------------------ uint64_t cta::RetrieveMount::getCapacityInBytes() const { if(!m_dbMount.get()) throw exception::Exception("In cta::RetrieveMount::getVendor(): got NULL dbMount"); @@ -135,9 +139,15 @@ std::list<std::unique_ptr<cta::RetrieveJob> > cta::RetrieveMount::getNextJobBatc log::LogContext& logContext) { if (!m_sessionRunning) throw SessionNotRunning("In RetrieveMount::getNextJobBatch(): trying to get job from complete/not started session"); - // Try and get a new job from the DB - std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob>> dbJobBatch(m_dbMount->getNextJobBatch(filesRequested, - bytesRequested, logContext)); + // Get the current file systems list from the catalogue + disk::DiskSystemList diskSystemList; + diskSystemList = m_catalogue.getAllDiskSystems(); + // TODO: the diskSystemFreeSpaceList could be made a member of the retrieve mount and cache the fetched values, limiting the re-querying + // of the disk systems free space. + disk::DiskSystemFreeSpaceList diskSystemFreeSpaceList (diskSystemList); + // Try and get a new job from the DB. The DB mount (in memory object) is taking care of reserving the free space for the popped + // elements and query the disk systems, via the diskSystemFreeSpaceList object. + auto dbJobBatch = m_dbMount->getNextJobBatch(filesRequested, bytesRequested, diskSystemFreeSpaceList, logContext); std::list<std::unique_ptr<RetrieveJob>> ret; // We prepare the response for (auto & sdrj: dbJobBatch) { @@ -214,7 +224,7 @@ void cta::RetrieveMount::flushAsyncSuccessReports(std::queue<std::unique_ptr<cta .add("diskFileId", job->archiveFile.diskFileId) .add("lastKnownDiskPath", job->archiveFile.diskFileInfo.path); } - const std::string msg_error="In ArchiveMount::reportJobsBatchWritten(): got an standard exception"; + const std::string msg_error="In RetrieveMount::reportJobsBatchWritten(): got an standard exception"; logContext.log(cta::log::ERR, msg_error); // Failing here does not really affect the session so we can carry on. Reported jobs are reported, non-reported ones // will be retried. diff --git a/scheduler/RetrieveMount.hpp b/scheduler/RetrieveMount.hpp index b8a5496777370801ea3040e57e8cf7616869f430..e5cb7b9028187436ff6ab4f596b0b6a5b2b1d06c 100644 --- a/scheduler/RetrieveMount.hpp +++ b/scheduler/RetrieveMount.hpp @@ -229,6 +229,11 @@ namespace cta { /** An initialized-once factory for archive reports (indirectly used by ArchiveJobs) */ disk::DiskReporterFactory m_reporterFactory; + /** + * Internal tracking of the full disk systems. It is one strike out (for the mount duration). + */ + std::set<std::string> m_fullDiskSystems; + /** * A pointer to the file catalogue. */ diff --git a/scheduler/Scheduler.cpp b/scheduler/Scheduler.cpp index 340e9c69241b22fb2da0819ab995cabe7561e6b9..8d108b2d1184f839fd81c75de06793f5938ce1e8 100644 --- a/scheduler/Scheduler.cpp +++ b/scheduler/Scheduler.cpp @@ -211,8 +211,14 @@ std::string Scheduler::queueRetrieve( // Get the queue criteria common::dataStructures::RetrieveFileQueueCriteria queueCriteria; queueCriteria = m_catalogue.prepareToRetrieveFile(instanceName, request.archiveFileID, request.requester, request.activity, lc); + auto diskSystemList = m_catalogue.getAllDiskSystems(); auto catalogueTime = t.secs(cta::utils::Timer::resetCounter); - auto requestInfo = m_db.queueRetrieve(request, queueCriteria, lc); + // Determine disk system for this request, if any. + optional<std::string> diskSystemName; + try { + diskSystemName = diskSystemList.getDSNAme(request.dstURL); + } catch (std::out_of_range&) {} + auto requestInfo = m_db.queueRetrieve(request, queueCriteria, diskSystemName, lc); auto schedulerDbTime = t.secs(); log::ScopedParamContainer spc(lc); spc.add("fileId", request.archiveFileID) @@ -624,8 +630,10 @@ void Scheduler::expandRepackRequest(std::unique_ptr<RepackRequest>& repackReques // We know that the fSeq processed on the tape are >= initial fSeq + filesCount - 1 (or fSeq - 1 as we counted). // We pass this information to the db for recording in the repack request. This will allow restarting from the right // value in case of crash. + auto diskSystemList = m_catalogue.getAllDiskSystems(); + timingList.insertAndReset("getDisksystemsListTime",t); try{ - nbRetrieveSubrequestsQueued = repackRequest->m_dbReq->addSubrequestsAndUpdateStats(retrieveSubrequests, archiveRoutesMap, fSeq, maxAddedFSeq, totalStatsFile, lc); + nbRetrieveSubrequestsQueued = repackRequest->m_dbReq->addSubrequestsAndUpdateStats(retrieveSubrequests, archiveRoutesMap, fSeq, maxAddedFSeq, totalStatsFile, diskSystemList, lc); } catch(const cta::ExpandRepackRequestException& e){ deleteRepackBuffer(std::move(dir)); throw e; @@ -958,12 +966,13 @@ void Scheduler::sortAndGetTapesForMountInfo(std::unique_ptr<SchedulerDatabase::T // We can now filter out the potential mounts for which their mount criteria // is not yet met, filter out the potential mounts for which the maximum mount - // quota is already reached, and weight the remaining by how much of their quota - // is reached + // quota is already reached, filter out the retrieve requests put to sleep for lack of disk space, + // and weight the remaining by how much of their quota is reached. for (auto m = mountInfo->potentialMounts.begin(); m!= mountInfo->potentialMounts.end();) { // Get summary data uint32_t existingMounts = 0; uint32_t activityMounts = 0; + bool sleepingMount = false; try { existingMounts = existingMountsSummary .at(TapePoolMountPair(m->tapePool, common::dataStructures::getMountBasicType(m->type))) @@ -986,7 +995,10 @@ void Scheduler::sortAndGetTapesForMountInfo(std::unique_ptr<SchedulerDatabase::T mountPassesACriteria = true; if (!effectiveExistingMounts && ((time(NULL) - m->oldestJobStartTime) > m->minRequestAge)) mountPassesACriteria = true; - if (!mountPassesACriteria || existingMounts >= m->maxDrivesAllowed) { + if (m->sleepingMount) { + sleepingMount = true; + } + if (!mountPassesACriteria || existingMounts >= m->maxDrivesAllowed || sleepingMount) { log::ScopedParamContainer params(lc); params.add("tapePool", m->tapePool); if ( m->type == common::dataStructures::MountType::Retrieve) { @@ -1002,6 +1014,7 @@ void Scheduler::sortAndGetTapesForMountInfo(std::unique_ptr<SchedulerDatabase::T .add("minArchiveRequestAge", m->minRequestAge) .add("existingMounts", existingMounts) .add("maxDrivesAllowed", m->maxDrivesAllowed); + if (sleepingMount) params.add("fullDiskSystem", m->diskSystemSleptFor); lc.log(log::DEBUG, "In Scheduler::sortAndGetTapesForMountInfo(): Removing potential mount not passing criteria"); m = mountInfo->potentialMounts.erase(m); } else { @@ -1508,6 +1521,13 @@ std::list<common::dataStructures::QueueAndMountSummary> Scheduler::getQueuesAndM summary.bytesQueued = pm.bytesQueued; summary.filesQueued = pm.filesQueued; summary.oldestJobAge = time(nullptr) - pm.oldestJobStartTime ; + if (pm.sleepingMount) { + common::dataStructures::QueueAndMountSummary::SleepForSpaceInfo sfsi; + sfsi.startTime = pm.sleepStartTime; + sfsi.diskSystemName = pm.diskSystemSleptFor; + sfsi.sleepTime = pm.sleepTime; + summary.sleepForSpaceInfo = sfsi; + } break; default: break; diff --git a/scheduler/SchedulerDatabase.cpp b/scheduler/SchedulerDatabase.cpp index 4c257ef242e7f756b4507b6d67f9cde89aaa26a5..4c1415c4ee8bba2578495e2d6d385c7eae8a03a2 100644 --- a/scheduler/SchedulerDatabase.cpp +++ b/scheduler/SchedulerDatabase.cpp @@ -32,4 +32,13 @@ SchedulerDatabase::RepackRequestStatistics::RepackRequestStatistics() { operator [](s) = 0; } +void SchedulerDatabase::DiskSpaceReservationRequest::addRequest(const std::string& diskSystemName, uint64_t size) { + try { + at(diskSystemName) += size; + } catch (std::out_of_range &) { + operator[](diskSystemName) = size; + } +} + + } //namespace cta diff --git a/scheduler/SchedulerDatabase.hpp b/scheduler/SchedulerDatabase.hpp index 1467274a02618d1ceff38a6f725669d0c0d59028..ee7ba1c4b41745b805f3a0e5adf94577d6ce5a75 100644 --- a/scheduler/SchedulerDatabase.hpp +++ b/scheduler/SchedulerDatabase.hpp @@ -34,6 +34,7 @@ #include "common/dataStructures/CancelRetrieveRequest.hpp" #include "common/dataStructures/RepackInfo.hpp" #include "common/dataStructures/SecurityIdentity.hpp" +#include "disk/DiskSystem.hpp" #include "common/remoteFS/RemotePathAndStatus.hpp" #include "common/exception/Exception.hpp" #include "common/log/LogContext.hpp" @@ -269,6 +270,7 @@ public: * @param rqst The request. * @param criteria The criteria retrieved from the CTA catalogue to be used to * decide how to quue the request. + * @param diskSystemName optional disk system name if the destination matches a declared one. * @param logContext context allowing logging db operation * @return the selected vid (mostly for logging) */ @@ -277,7 +279,8 @@ public: std::string requestId; }; virtual RetrieveRequestInfo queueRetrieve(cta::common::dataStructures::RetrieveRequest &rqst, - const cta::common::dataStructures::RetrieveFileQueueCriteria &criteria, log::LogContext &logContext) = 0; + const cta::common::dataStructures::RetrieveFileQueueCriteria &criteria, const optional<std::string> diskSystemName, + log::LogContext &logContext) = 0; virtual void cancelRetrieve(const std::string & instanceName, const cta::common::dataStructures::CancelRetrieveRequest &rqst, log::LogContext & lc) = 0; @@ -347,6 +350,12 @@ public: /*============ Retrieve management: tape server side ======================*/ class RetrieveJob; + + struct DiskSpaceReservationRequest: public std::map<std::string, uint64_t> { + void addRequest(const std::string &diskSystemName, uint64_t size); + }; + +public: class RetrieveMount { public: struct MountInfo { @@ -364,7 +373,7 @@ public: } mountInfo; virtual const MountInfo & getMountInfo() = 0; virtual std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob>> getNextJobBatch(uint64_t filesRequested, - uint64_t bytesRequested, log::LogContext& logContext) = 0; + uint64_t bytesRequested, cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, log::LogContext& logContext) = 0; virtual void complete(time_t completionTime) = 0; virtual void setDriveStatus(common::dataStructures::DriveStatus status, time_t completionTime) = 0; virtual void setTapeSessionStats(const castor::tape::tapeserver::daemon::TapeSessionStats &stats) = 0; @@ -385,6 +394,7 @@ public: } reportType; cta::common::dataStructures::ArchiveFile archiveFile; cta::common::dataStructures::RetrieveRequest retrieveRequest; + optional<std::string> diskSystemName; uint32_t selectedCopyNb; bool isRepack = false; /** Set the job successful (async). Wait() and end of report happen in RetrieveMount::flushAsyncSuccessReports() */ @@ -463,9 +473,11 @@ public: /** * Add Retrieve subrequests to the repack request and update its statistics * @return the number of retrieve subrequests queued - */ + */ virtual uint64_t addSubrequestsAndUpdateStats(std::list<Subrequest>& repackSubrequests, - cta::common::dataStructures::ArchiveRoute::FullMap & archiveRoutesMap, uint64_t maxFSeqLowBound, const uint64_t maxAddedFSeq, const TotalStatsFiles &totalStatsFiles, log::LogContext & lc) = 0; + cta::common::dataStructures::ArchiveRoute::FullMap & archiveRoutesMap, uint64_t maxFSeqLowBound, + const uint64_t maxAddedFSeq, const TotalStatsFiles &totalStatsFiles, disk::DiskSystemList diskSystemList, + log::LogContext & lc) = 0; virtual void expandDone() = 0; virtual void fail() = 0; virtual void requeueInToExpandQueue(log::LogContext &lc) = 0; @@ -565,6 +577,10 @@ public: std::string logicalLibrary; /**< The logical library (for a retrieve) */ double ratioOfMountQuotaUsed; /**< The [ 0.0, 1.0 ] ratio of existing * mounts/quota (for faire share of mounts)*/ + bool sleepingMount = false; /**< Is the mount being slept due to lack of disk space? */ + time_t sleepStartTime = 0; /**< Start time for the sleeping for lack of disk space. */ + std::string diskSystemSleptFor;/**< Name of (one of) the disk system(s) that could was too full to start more retrieves. */ + uint64_t sleepTime = 0; /**< Length of time to be slept for for this disk system. */ uint32_t mountCount; /**< The number of mounts for this tape pool (which is the current "chargeable" entity for quotas. */ struct ActivityNameAndWeightedMountCount { std::string activity; diff --git a/scheduler/SchedulerDatabaseTest.cpp b/scheduler/SchedulerDatabaseTest.cpp index 178dd76f62914cc351175e89e733509b5f1e0cd0..3fa680ab30fb02e38490d8441689e533eb7b6032 100644 --- a/scheduler/SchedulerDatabaseTest.cpp +++ b/scheduler/SchedulerDatabaseTest.cpp @@ -24,6 +24,7 @@ #include "OStoreDB/OStoreDBFactory.hpp" #include "objectstore/BackendRados.hpp" #include "common/log/DummyLogger.hpp" +#include "common/range.hpp" #include "common/make_unique.hpp" #ifdef STDOUT_LOGGING #include "common/log/StdoutLogger.hpp" @@ -189,7 +190,7 @@ TEST_P(SchedulerDatabaseTest, createManyArchiveJobs) { }); jobInsertions.emplace_back(std::async(std::launch::async,lambdas.back())); } - for (auto &j: jobInsertions) { j.wait(); } + for (auto &j: jobInsertions) { j.get(); } jobInsertions.clear(); lambdas.clear(); db.waitSubthreadsComplete(); @@ -269,7 +270,7 @@ TEST_P(SchedulerDatabaseTest, createManyArchiveJobs) { }); jobInsertions.emplace_back(std::async(std::launch::async,lambdas.back())); } - for (auto &j: jobInsertions) { j.wait(); } + for (auto &j: jobInsertions) { j.get(); } jobInsertions.clear(); lambdas.clear(); @@ -307,6 +308,189 @@ TEST_P(SchedulerDatabaseTest, createManyArchiveJobs) { moutInfo.reset(nullptr); } + +TEST_P(SchedulerDatabaseTest, popRetrieveRequestsWithDisksytem) { + using namespace cta; +#ifndef STDOUT_LOGGING + cta::log::DummyLogger dl("", ""); +#else + cta::log::StdoutLogger dl("", ""); +#endif + cta::log::LogContext lc(dl); + + cta::SchedulerDatabase &db = getDb(); + + // Create the disk system list + cta::disk::DiskSystemList diskSystemList; + diskSystemList.push_back(cta::disk::DiskSystem{"ds-A", "$root://a.disk.system/", "constantFreeSpace:999999999999", 60, 10UL*1000*1000*1000, + 15*60, common::dataStructures::EntryLog(), common::dataStructures::EntryLog{},"No comment"}); + diskSystemList.push_back(cta::disk::DiskSystem{"ds-B", "$root://b.disk.system/", "constantFreeSpace:999999999999", 60, 10UL*1000*1000*1000, + 15*60, common::dataStructures::EntryLog(), common::dataStructures::EntryLog{},"No comment"}); + cta::disk::DiskSystemFreeSpaceList diskSystemFreeSpaceList(diskSystemList); + + // Inject 10 retrieve jobs to the db. + const size_t filesToDo = 10; + std::list<std::future<void>> jobInsertions; + std::list<std::function<void()>> lambdas; + for (auto i: cta::range<size_t>(filesToDo)) { + lambdas.emplace_back( + [i,&db,&lc,diskSystemList](){ + cta::common::dataStructures::RetrieveRequest rr; + cta::log::LogContext locallc=lc; + cta::common::dataStructures::RetrieveFileQueueCriteria rfqc; + rfqc.mountPolicy.name = "mountPolicy"; + rfqc.mountPolicy.archivePriority = 1; + rfqc.mountPolicy.archiveMinRequestAge = 0; + rfqc.mountPolicy.retrievePriority = 1; + rfqc.mountPolicy.retrieveMinRequestAge = 0; + rfqc.mountPolicy.maxDrivesAllowed = 10; + rfqc.mountPolicy.creationLog = { "u", "h", time(nullptr)}; + rfqc.mountPolicy.lastModificationLog = { "u", "h", time(nullptr)}; + rfqc.mountPolicy.comment = "comment"; + rfqc.archiveFile.fileSize = 1000; + rfqc.archiveFile.tapeFiles.push_back(cta::common::dataStructures::TapeFile()); + rfqc.archiveFile.tapeFiles.back().fSeq = i; + rfqc.archiveFile.tapeFiles.back().vid = "vid"; + rr.creationLog = { "user", "host", time(nullptr)}; + uuid_t fileUUID; + uuid_generate(fileUUID); + char fileUUIDStr[37]; + uuid_unparse(fileUUID, fileUUIDStr); + rr.diskFileInfo.path = std::string("/uuid/")+fileUUIDStr; + rr.requester = { "user", "group" }; + rr.dstURL = std::string ("root://") + (i%2?"b":"a") + ".disk.system/" + std::to_string(i); + std::string dsName = (i%2?"ds-B":"ds-A"); + db.queueRetrieve(rr, rfqc, dsName, locallc); + }); + jobInsertions.emplace_back(std::async(std::launch::async,lambdas.back())); + } + for (auto &j: jobInsertions) { j.get(); } + jobInsertions.clear(); + lambdas.clear(); + db.waitSubthreadsComplete(); + + // Then load all archive jobs into memory + // Create mount. + auto moutInfo = db.getMountInfo(lc); + ASSERT_EQ(1, moutInfo->potentialMounts.size()); + auto rm=moutInfo->createRetrieveMount("vid", "tapePool", "drive", "library", "host", "vo","mediaType", "vendor",123456789,time(nullptr), cta::nullopt); + auto rjb = rm->getNextJobBatch(20,20*1000,diskSystemFreeSpaceList, lc); + ASSERT_EQ(filesToDo, rjb.size()); + std::list <cta::SchedulerDatabase::RetrieveJob*> jobBatch; + for (auto &rj: rjb) { + rj->asyncSetSuccessful(); + ASSERT_TRUE((bool)rj->diskSystemName); + ASSERT_EQ(rj->archiveFile.tapeFiles.front().fSeq%2?"ds-B":"ds-A", rj->diskSystemName.value()); + jobBatch.emplace_back(rj.get()); + } + rm->flushAsyncSuccessReports(jobBatch, lc); + rjb.clear(); + ASSERT_EQ(0, rm->getNextJobBatch(20,20*1000,diskSystemFreeSpaceList, lc).size()); + rm->complete(time(nullptr)); + rm.reset(nullptr); + moutInfo.reset(nullptr); +} + +TEST_P(SchedulerDatabaseTest, popRetrieveRequestsWithBackpressure) { + using namespace cta; +#ifndef STDOUT_LOGGING + cta::log::DummyLogger dl("", ""); +#else + cta::log::StdoutLogger dl("", ""); +#endif + cta::log::LogContext lc(dl); + + cta::SchedulerDatabase &db = getDb(); + + // Create the disk system list + cta::disk::DiskSystemList diskSystemList; + // Files are 5 * 1000 byte per disk system. Make sure only half fits, preventing the pop for one of them. + // We should then be able to pop one bacth of for a single disk system, and get the queue slept. + diskSystemList.push_back(cta::disk::DiskSystem{"ds-A", "$root://a.disk.system/", "constantFreeSpace:6000", 60, 0UL, + 15*60, common::dataStructures::EntryLog(), common::dataStructures::EntryLog{},"No comment"}); + diskSystemList.push_back(cta::disk::DiskSystem{"ds-B", "$root://b.disk.system/", "constantFreeSpace:7500", 60, 5000UL, + 15*60, common::dataStructures::EntryLog(), common::dataStructures::EntryLog{},"No comment"}); + cta::disk::DiskSystemFreeSpaceList diskSystemFreeSpaceList(diskSystemList); + + + // Inject 10 retrieve jobs to the db. + const size_t filesToDo = 10; + std::atomic<size_t> aFiles(0); + std::atomic<size_t> bFiles(0); + std::list<std::future<void>> jobInsertions; + std::list<std::function<void()>> lambdas; + for (auto i: cta::range<size_t>(filesToDo)) { + lambdas.emplace_back( + [i,&db,&lc,&aFiles, &bFiles, diskSystemList](){ + cta::common::dataStructures::RetrieveRequest rr; + cta::log::LogContext locallc=lc; + cta::common::dataStructures::RetrieveFileQueueCriteria rfqc; + rfqc.mountPolicy.name = "mountPolicy"; + rfqc.mountPolicy.archivePriority = 1; + rfqc.mountPolicy.archiveMinRequestAge = 0; + rfqc.mountPolicy.retrievePriority = 1; + rfqc.mountPolicy.retrieveMinRequestAge = 0; + rfqc.mountPolicy.maxDrivesAllowed = 10; + rfqc.mountPolicy.creationLog = { "u", "h", time(nullptr)}; + rfqc.mountPolicy.lastModificationLog = { "u", "h", time(nullptr)}; + rfqc.mountPolicy.comment = "comment"; + rfqc.archiveFile.fileSize = 1000; + rfqc.archiveFile.tapeFiles.push_back(cta::common::dataStructures::TapeFile()); + rfqc.archiveFile.tapeFiles.back().fSeq = i; + rfqc.archiveFile.tapeFiles.back().vid = "vid"; + rr.creationLog = { "user", "host", time(nullptr)}; + uuid_t fileUUID; + uuid_generate(fileUUID); + char fileUUIDStr[37]; + uuid_unparse(fileUUID, fileUUIDStr); + rr.diskFileInfo.path = std::string("/uuid/")+fileUUIDStr; + rr.requester = { "user", "group" }; + std::string dsName; + if (i%2) { + rr.dstURL = std::string("root://b.disk.system/") + std::to_string(i); + dsName = "ds-B"; + ++bFiles; + } else { + rr.dstURL = std::string("root://a.disk.system/") + std::to_string(i); + dsName = "ds-A"; + ++aFiles; + } + db.queueRetrieve(rr, rfqc, dsName, locallc); + }); + jobInsertions.emplace_back(std::async(std::launch::async,lambdas.back())); + } + for (auto &j: jobInsertions) { j.get(); } + jobInsertions.clear(); + lambdas.clear(); + db.waitSubthreadsComplete(); + + // Then load all archive jobs into memory + // Create mount. + auto moutInfo = db.getMountInfo(lc); + ASSERT_EQ(1, moutInfo->potentialMounts.size()); + auto rm=moutInfo->createRetrieveMount("vid", "tapePool", "drive", "library", "host", "vo","mediaType", "vendor",123456789,time(nullptr), cta::nullopt); + auto rjb = rm->getNextJobBatch(20,20*1000,diskSystemFreeSpaceList, lc); + ASSERT_EQ(aFiles, rjb.size()); + std::list <cta::SchedulerDatabase::RetrieveJob*> jobBatch; + for (auto &rj: rjb) { + rj->asyncSetSuccessful(); + ASSERT_TRUE((bool)rj->diskSystemName); + ASSERT_EQ(rj->archiveFile.tapeFiles.front().fSeq%2?"ds-B":"ds-A", rj->diskSystemName.value()); + jobBatch.emplace_back(rj.get()); + } + rm->flushAsyncSuccessReports(jobBatch, lc); + rjb.clear(); + ASSERT_EQ(0, rm->getNextJobBatch(20,20*1000,diskSystemFreeSpaceList, lc).size()); + rm->complete(time(nullptr)); + rm.reset(nullptr); + moutInfo.reset(nullptr); + auto mi = db.getMountInfoNoLock(lc); + ASSERT_EQ(1, mi->potentialMounts.size()); + ASSERT_EQ(bFiles, mi->potentialMounts.begin()->filesQueued); + ASSERT_TRUE(mi->potentialMounts.begin()->sleepingMount); + ASSERT_EQ("ds-B", mi->potentialMounts.begin()->diskSystemSleptFor); +} + #undef TEST_MOCK_DB #ifdef TEST_MOCK_DB static cta::MockSchedulerDatabaseFactory mockDbFactory; diff --git a/scheduler/SchedulerTest.cpp b/scheduler/SchedulerTest.cpp index d7af0f0dfe54cfa26d5a90615aef7cdae56529c8..0d35428ba4da9a1f735bab6af76b7e52c73509b1 100644 --- a/scheduler/SchedulerTest.cpp +++ b/scheduler/SchedulerTest.cpp @@ -1389,6 +1389,7 @@ TEST_P(SchedulerTest, expandRepackRequest) { auto &schedulerDB = getSchedulerDB(); setupDefaultCatalogue(); + catalogue.createDiskSystem({"user", "host"}, "diskSystem", "/public_dir/public_file", "constantFreeSpace:10", 10, 10L*1000*1000*1000, 15*60, "no comment"); #ifdef STDOUT_LOGGING diff --git a/tapeserver/castor/tape/tapeserver/daemon/DiskWriteTaskTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DiskWriteTaskTest.cpp index f7b3c8e2db5d0c41192bb41757439ac17384e824..a49fc4bb658bb0803814a71e91df8b7495143b13 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DiskWriteTaskTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DiskWriteTaskTest.cpp @@ -42,7 +42,9 @@ namespace unitTests{ class TestingDatabaseRetrieveMount: public cta::SchedulerDatabase::RetrieveMount { const MountInfo & getMountInfo() override { throw std::runtime_error("Not implemented"); } - std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} + std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, + cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} + void complete(time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setDriveStatus(cta::common::dataStructures::DriveStatus status, time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setTapeSessionStats(const castor::tape::tapeserver::daemon::TapeSessionStats &stats) override { throw std::runtime_error("Not implemented"); } diff --git a/tapeserver/castor/tape/tapeserver/daemon/DiskWriteThreadPoolTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DiskWriteThreadPoolTest.cpp index b758bd62753da181d249f3a011fcb8b943acf9f0..6ec2d62b6bfdba5c24dca0400952257fc96577a7 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DiskWriteThreadPoolTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DiskWriteThreadPoolTest.cpp @@ -37,7 +37,8 @@ namespace unitTests{ class TestingDatabaseRetrieveMount: public cta::SchedulerDatabase::RetrieveMount { const MountInfo & getMountInfo() override { throw std::runtime_error("Not implemented"); } - std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} + std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} + void complete(time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setDriveStatus(cta::common::dataStructures::DriveStatus status, time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setTapeSessionStats(const castor::tape::tapeserver::daemon::TapeSessionStats &stats) override { throw std::runtime_error("Not implemented"); } diff --git a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp index ba992cd264588eb1879fe975254900d6601dbe19..cbcea00193aaa3a43e618c48f52c2305c30e3487 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/RecallTaskInjectorTest.cpp @@ -131,7 +131,8 @@ namespace unitTests class TestingDatabaseRetrieveMount: public cta::SchedulerDatabase::RetrieveMount { const MountInfo & getMountInfo() override { throw std::runtime_error("Not implemented"); } - std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} + std::list<std::unique_ptr<cta::SchedulerDatabase::RetrieveJob> > getNextJobBatch(uint64_t filesRequested, uint64_t bytesRequested, + cta::disk::DiskSystemFreeSpaceList & diskSystemFreeSpace, cta::log::LogContext& logContext) override { throw std::runtime_error("Not implemented");} void complete(time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setDriveStatus(cta::common::dataStructures::DriveStatus status, time_t completionTime) override { throw std::runtime_error("Not implemented"); } void setTapeSessionStats(const castor::tape::tapeserver::daemon::TapeSessionStats &stats) override { throw std::runtime_error("Not implemented"); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fdde0f00a130b539ac1114f22ef710140d46fc45..fd7308506026681fd3f92a69fda01c0818b81153 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -63,6 +63,7 @@ target_link_libraries(cta-rdbmsUnitTests ctadbconfigconnunittests ctadbconfigstmtunittests ctardbmsunittests + ctadisk gtest pthread ${PROTOBUF3_LIBRARIES}) diff --git a/tests/postgresqlUnitTests.sh b/tests/postgresqlUnitTests.sh new file mode 100755 index 0000000000000000000000000000000000000000..34a86c07af82ac433deb532069ec6f09b44e1d50 --- /dev/null +++ b/tests/postgresqlUnitTests.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Utility script to run postgresql tests like in CI. + +if ! rpm -q --quiet rh-postgresql96-postgresql-server; then + echo "Please install the package rh-postgresql96-postgresql-server to run this script." + exit 1 +fi + +if [[ ! ( -x ./catalogue/cta-catalogue-schema-create && -x ./tests/cta-rdbmsUnitTests ) ]]; then + echo "Please run this script from a build tree of CTA." + exit 1 +fi + +if [[ $(id -u) != "0" ]]; then + echo "Please run this script as root (sudo)." + exit 1; +fi + +POSTGRESQL_DATA_DIR=/usr/local/cta_test_pgsql_data +POSTGRESQL_LOG_DIR=/var/log/postgres +echo POSTGRESQL_DATA_DIR=${POSTGRESQL_DATA_DIR} +echo POSTGRESQL_LOG_DIR=${POSTGRESQL_LOG_DIR} +mkdir -p ${POSTGRESQL_DATA_DIR} +chown -R postgres:postgres ${POSTGRESQL_DATA_DIR} +mkdir -p ${POSTGRESQL_LOG_DIR} +chown -R postgres:postgres ${POSTGRESQL_LOG_DIR} +export LD_LIBRARY_PATH=/opt/rh/rh-postgresql96/root/usr/lib64 +POSTGRES_BIN=/opt/rh/rh-postgresql96/root/usr/bin +echo POSTGRES_BIN=${POSTGRES_BIN} +(cd / ; +runuser -u postgres -- ${POSTGRES_BIN}/initdb -D ${POSTGRESQL_DATA_DIR} +runuser -u postgres -- ${POSTGRES_BIN}/pg_ctl start -w -t 10 -D ${POSTGRESQL_DATA_DIR} -l ${POSTGRESQL_LOG_DIR}/cta_test_postgres.log +runuser -u postgres -- ${POSTGRES_BIN}/createdb cta +runuser -u postgres -- ${POSTGRES_BIN}/createuser -E cta +) +CTA_CATALOGUE_CONF=`mktemp` +echo CTA_CATALOGUE_CONF=${CTA_CATALOGUE_CONF} +echo 'postgresql:postgresql://cta@localhost/cta' > ${CTA_CATALOGUE_CONF} +./catalogue/cta-catalogue-schema-create ${CTA_CATALOGUE_CONF} +./tests/cta-rdbmsUnitTests ${CTA_CATALOGUE_CONF} +(cd / ; runuser -u postgres -- ${POSTGRES_BIN}/pg_ctl stop -D ${POSTGRESQL_DATA_DIR}) +rm -rf ${POSTGRESQL_DATA_DIR} \ No newline at end of file diff --git a/xroot_plugins/XrdCtaDiskSystemLs.hpp b/xroot_plugins/XrdCtaDiskSystemLs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..79644ee4c219ab12be32318aea7b99a2de3229df --- /dev/null +++ b/xroot_plugins/XrdCtaDiskSystemLs.hpp @@ -0,0 +1,107 @@ +/*! + * @project The CERN Tape Archive (CTA) + * @brief CTA TapePool Ls stream implementation + * @copyright Copyright 2019 CERN + * @license This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <xroot_plugins/XrdCtaStream.hpp> +#include <xroot_plugins/XrdSsiCtaRequestMessage.hpp> + +#include "disk/DiskSystem.hpp" + + +namespace cta { namespace xrd { + +/*! + * Stream object which implements "disksystem ls" command + */ +class DiskSystemLsStream: public XrdCtaStream{ +public: + /*! + * Constructor + * + * @param[in] requestMsg RequestMessage containing command-line arguments + * @param[in] catalogue CTA Catalogue + * @param[in] scheduler CTA Scheduler + */ + DiskSystemLsStream(const RequestMessage &requestMsg, cta::catalogue::Catalogue &catalogue, cta::Scheduler &scheduler); + +private: + /*! + * Can we close the stream? + */ + virtual bool isDone() const { + return m_diskSystemList.empty(); + } + + /*! + * Fill the buffer + */ + virtual int fillBuffer(XrdSsiPb::OStreamBuffer<Data> *streambuf); + + cta::disk::DiskSystemList m_diskSystemList; //!< List of disk systems from the catalogue + + static constexpr const char* const LOG_SUFFIX = "DiskSystemLsStream"; //!< Identifier for log messages +}; + + +DiskSystemLsStream::DiskSystemLsStream(const RequestMessage &requestMsg, cta::catalogue::Catalogue &catalogue, cta::Scheduler &scheduler) : + XrdCtaStream(catalogue, scheduler), + m_diskSystemList(catalogue.getAllDiskSystems()) +{ + using namespace cta::admin; + + XrdSsiPb::Log::Msg(XrdSsiPb::Log::DEBUG, LOG_SUFFIX, "DiskSystemLsStream() constructor"); +} + + std::string name; + std::string fileRegexp; + std::string freeSpaceQueryURL; + uint64_t refreshInterval; + uint64_t targetedFreeSpace; + + cta::common::dataStructures::EntryLog creationLog; + cta::common::dataStructures::EntryLog lastModificationLog; + std::string comment; + +int DiskSystemLsStream::fillBuffer(XrdSsiPb::OStreamBuffer<Data> *streambuf) { + for(bool is_buffer_full = false; !m_diskSystemList.empty() && !is_buffer_full; m_diskSystemList.pop_front()) { + Data record; + + auto &ds = m_diskSystemList.front(); + auto ds_item = record.mutable_dsls_item(); + + ds_item->set_name(ds.name); + ds_item->set_file_regexp(ds.fileRegexp); + ds_item->set_free_space_query_url(ds.freeSpaceQueryURL); + ds_item->set_refresh_interval(ds.refreshInterval); + ds_item->set_targeted_free_space(ds.targetedFreeSpace); + ds_item->set_sleep_time(ds.sleepTime); + ds_item->mutable_creation_log()->set_username(ds.creationLog.username); + ds_item->mutable_creation_log()->set_host(ds.creationLog.host); + ds_item->mutable_creation_log()->set_time(ds.creationLog.time); + ds_item->mutable_last_modification_log()->set_username(ds.lastModificationLog.username); + ds_item->mutable_last_modification_log()->set_host(ds.lastModificationLog.host); + ds_item->mutable_last_modification_log()->set_time(ds.lastModificationLog.time); + ds_item->set_comment(ds.comment); + + is_buffer_full = streambuf->Push(record); + } + return streambuf->Size(); +} + +}} // namespace cta::xrd diff --git a/xroot_plugins/XrdCtaShowQueues.hpp b/xroot_plugins/XrdCtaShowQueues.hpp index 72bfd88cabe01439be4705414fa8277085350b23..f2a86590bff844ca74d135124b0440c931463192 100644 --- a/xroot_plugins/XrdCtaShowQueues.hpp +++ b/xroot_plugins/XrdCtaShowQueues.hpp @@ -112,6 +112,13 @@ int ShowQueuesStream::fillBuffer(XrdSsiPb::OStreamBuffer<Data> *streambuf) { sq_item->set_disabled_tapes(sq.disabledTapes); sq_item->set_rdonly_tapes(sq.readOnlyTapes); sq_item->set_writable_tapes(sq.writableTapes); + if (sq.sleepForSpaceInfo) { + sq_item->set_sleeping_for_space(true); + sq_item->set_sleep_start_time(sq.sleepForSpaceInfo.value().startTime); + sq_item->set_disk_system_slept_for(sq.sleepForSpaceInfo.value().diskSystemName); + } else { + sq_item->set_sleeping_for_space(false); + } is_buffer_full = streambuf->Push(record); } diff --git a/xroot_plugins/XrdSsiCtaRequestMessage.cpp b/xroot_plugins/XrdSsiCtaRequestMessage.cpp index f7b4454675acf69134aa213cdabd38a2f443e6c1..0e194a60e3f032c1598c49a07dc06bb26dea6c0b 100644 --- a/xroot_plugins/XrdSsiCtaRequestMessage.cpp +++ b/xroot_plugins/XrdSsiCtaRequestMessage.cpp @@ -36,6 +36,7 @@ using XrdSsiPb::PbException; #include "XrdCtaTapeLs.hpp" #include "XrdCtaStorageClassLs.hpp" #include "XrdCtaTapePoolLs.hpp" +#include "XrdCtaDiskSystemLs.hpp" namespace cta { namespace xrd { @@ -230,7 +231,19 @@ void RequestMessage::process(const cta::xrd::Request &request, cta::xrd::Respons case cmd_pair(AdminCmd::CMD_TAPEPOOL, AdminCmd::SUBCMD_LS): processTapePool_Ls(response, stream); break; - + case cmd_pair(AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_LS): + processDiskSystem_Ls(response, stream); + break; + case cmd_pair(AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_ADD): + processDiskSystem_Add(response); + break; + case cmd_pair(AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_RM): + processDiskSystem_Rm(response); + break; + case cmd_pair(AdminCmd::CMD_DISKSYSTEM, AdminCmd::SUBCMD_CH): + processDiskSystem_Ch(response); + break; + default: throw PbException("Admin command pair <" + AdminCmd_Cmd_Name(request.admincmd().cmd()) + ", " + @@ -1523,6 +1536,80 @@ void RequestMessage::processTapePool_Ls(cta::xrd::Response &response, XrdSsiStre +void RequestMessage::processDiskSystem_Ls(cta::xrd::Response &response, XrdSsiStream* &stream) +{ + using namespace cta::admin; + + // Create a XrdSsi stream object to return the results + stream = new DiskSystemLsStream(*this, m_catalogue, m_scheduler); + + response.set_show_header(HeaderType::DISKSYSTEM_LS); + response.set_type(cta::xrd::Response::RSP_SUCCESS); +} + +void RequestMessage::processDiskSystem_Add(cta::xrd::Response &response) +{ + using namespace cta::admin; + + const auto &name = getRequired(OptionString::DISK_SYSTEM); + const auto &fileRegexp = getRequired(OptionString::FILE_REGEXP); + const auto &freeSpaceQueryURL = getRequired(OptionString::FREE_SPACE_QUERY_URL); + const auto &refreshInterval = getRequired(OptionUInt64::REFRESH_INTERVAL); + const auto &targetedFreeSpace = getRequired(OptionUInt64::TARGETED_FREE_SPACE); + const auto &sleepTime = getRequired(OptionUInt64::SLEEP_TIME); + const auto &comment = getRequired(OptionString::COMMENT); + + m_catalogue.createDiskSystem(m_cliIdentity, name, fileRegexp, freeSpaceQueryURL, + refreshInterval, targetedFreeSpace, sleepTime, comment); + + response.set_type(cta::xrd::Response::RSP_SUCCESS); +} + +void RequestMessage::processDiskSystem_Ch(cta::xrd::Response &response) +{ + using namespace cta::admin; + + const auto &name = getRequired(OptionString::DISK_SYSTEM); + const auto &fileRegexp = getOptional(OptionString::FILE_REGEXP); + const auto &freeSpaceQueryURL = getOptional(OptionString::FREE_SPACE_QUERY_URL); + const auto refreshInterval = getOptional(OptionUInt64::REFRESH_INTERVAL); + const auto targetedFreeSpace = getOptional(OptionUInt64::TARGETED_FREE_SPACE); + const auto sleepTime = getOptional(OptionUInt64::SLEEP_TIME); + const auto comment = getOptional(OptionString::COMMENT); + + if(comment) { + m_catalogue.modifyDiskSystemComment(m_cliIdentity, name, comment.value()); + } + if(fileRegexp) { + m_catalogue.modifyDiskSystemFileRegexp(m_cliIdentity, name, fileRegexp.value()); + } + if(freeSpaceQueryURL) { + m_catalogue.modifyDiskSystemFreeSpaceQueryURL(m_cliIdentity, name, freeSpaceQueryURL.value()); + } + if (sleepTime) { + m_catalogue.modifyDiskSystemSleepTime(m_cliIdentity, name, sleepTime.value()); + } + if(refreshInterval) { + m_catalogue.modifyDiskSystemRefreshInterval(m_cliIdentity, name, refreshInterval.value()); + } + if(targetedFreeSpace) { + m_catalogue.modifyDiskSystemTargetedFreeSpace(m_cliIdentity, name, targetedFreeSpace.value()); + } + + response.set_type(cta::xrd::Response::RSP_SUCCESS); +} + +void RequestMessage::processDiskSystem_Rm(cta::xrd::Response &response) +{ + using namespace cta::admin; + + const auto &name = getRequired(OptionString::DISK_SYSTEM); + + m_catalogue.deleteDiskSystem(name); + + response.set_type(cta::xrd::Response::RSP_SUCCESS); +} + std::string RequestMessage::setDriveState(const std::string ®ex, DriveState drive_state) { using namespace cta::admin; diff --git a/xroot_plugins/XrdSsiCtaRequestMessage.hpp b/xroot_plugins/XrdSsiCtaRequestMessage.hpp index 89eb5e68a24ce177723d3b5508720ec0bb270726..fb4774aeefa69221f73cd978c7d97c8e5fd6da96 100644 --- a/xroot_plugins/XrdSsiCtaRequestMessage.hpp +++ b/xroot_plugins/XrdSsiCtaRequestMessage.hpp @@ -185,7 +185,10 @@ private: void processTapePool_Add (cta::xrd::Response &response); void processTapePool_Ch (cta::xrd::Response &response); void processTapePool_Rm (cta::xrd::Response &response); - + void processDiskSystem_Add (cta::xrd::Response &response); + void processDiskSystem_Ch (cta::xrd::Response &response); + void processDiskSystem_Rm (cta::xrd::Response &response); + /*! * Process AdminCmd events which can return a stream response * @@ -211,6 +214,7 @@ private: admincmdstream_t processTapePool_Ls; admincmdstream_t processTape_Ls; admincmdstream_t processRepack_Ls; + admincmdstream_t processDiskSystem_Ls; /*! * Log an admin command