/************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2016 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ //------------------------------------------------------------------------------ //! @author Elvin-Alin Sindrilaru //! @brief FileSystemView test //------------------------------------------------------------------------------ #define IN_TEST_HARNESS #include "namespace/ns_quarkdb/accounting/SetChangeList.hh" #include "namespace/ns_quarkdb/accounting/FileSystemView.hh" #include "namespace/ns_quarkdb/accounting/FileSystemHandler.hh" #include "namespace/ns_quarkdb/persistency/ContainerMDSvc.hh" #include "namespace/ns_quarkdb/persistency/RequestBuilder.hh" #include "namespace/ns_quarkdb/persistency/FileMDSvc.hh" #include "namespace/ns_quarkdb/views/HierarchicalView.hh" #include "namespace/ns_quarkdb/tests/TestUtils.hh" #include "namespace/utils/RmrfHelper.hh" #include #include #include #include #include #include #include #include #undef IN_TEST_HARNESS //------------------------------------------------------------------------------ // Randomize a location //------------------------------------------------------------------------------ eos::IFileMD::location_t getRandomLocation() { return 1 + random() % 50; } //------------------------------------------------------------------------------ // Count replicas //------------------------------------------------------------------------------ size_t countReplicas(eos::IFsView* fs) { size_t replicas = 0; for (auto it = fs->getFileSystemIterator(); it->valid(); it->next()) { replicas += fs->getNumFilesOnFs(it->getElement()); } return replicas; } //------------------------------------------------------------------------------ // Count unlinked //------------------------------------------------------------------------------ size_t countUnlinked(eos::IFsView* fs) { size_t unlinked = 0; for (auto it = fs->getFileSystemIterator(); it->valid(); it->next()) { unlinked += fs->getNumUnlinkedFilesOnFs(it->getElement()); } return unlinked; } //------------------------------------------------------------------------------ // Test utility classes //------------------------------------------------------------------------------ TEST(FileSystemView, FileSetKey) { ASSERT_EQ(eos::RequestBuilder::keyFilesystemFiles(50), "fsview:50:files"); ASSERT_EQ(eos::RequestBuilder::keyFilesystemFiles(123), "fsview:123:files"); ASSERT_EQ(eos::RequestBuilder::keyFilesystemUnlinked(10), "fsview:10:unlinked"); ASSERT_EQ(eos::RequestBuilder::keyFilesystemUnlinked(999), "fsview:999:unlinked"); } TEST(FileSystemView, ParseFsId) { eos::IFileMD::location_t fsid; bool unlinked; ASSERT_TRUE(eos::parseFsId("fsview:1:files", fsid, unlinked)); ASSERT_EQ(fsid, 1); ASSERT_FALSE(unlinked); ASSERT_TRUE(eos::parseFsId("fsview:999:unlinked", fsid, unlinked)); ASSERT_EQ(fsid, 999); ASSERT_TRUE(unlinked); ASSERT_FALSE(eos::parseFsId("fsview:9:99:unlinked", fsid, unlinked)); ASSERT_FALSE(eos::parseFsId("fsview:999:uNlinked", fsid, unlinked)); ASSERT_FALSE(eos::parseFsId("fsVIew:1337:unlinked", fsid, unlinked)); } //------------------------------------------------------------------------------ // Concrete implementation tests //------------------------------------------------------------------------------ class FileSystemViewF : public eos::ns::testing::NsTestsFixture {}; TEST_F(FileSystemViewF, BasicSanity) { srandom(time(nullptr)); view()->createContainer("/test/embed/embed1", true); std::shared_ptr c = view()->createContainer("/test/embed/embed2", true); view()->createContainer("/test/embed/embed3", true); // Create some files for (int i = 0; i < 1000; ++i) { std::ostringstream o; o << "file" << i; std::shared_ptr files[4]; files[0] = view()->createFile(std::string("/test/embed/") + o.str()); files[1] = view()->createFile(std::string("/test/embed/embed1/") + o.str()); files[2] = view()->createFile(std::string("/test/embed/embed2/") + o.str()); files[3] = view()->createFile(std::string("/test/embed/embed3/") + o.str()); for (int j = 0; j < 4; ++j) { while (files[j]->getNumLocation() != 5) { files[j]->addLocation(getRandomLocation()); } view()->updateFileStore(files[j].get()); } } // Create some file without replicas assigned for (int i = 0; i < 500; ++i) { std::ostringstream o; o << "noreplicasfile" << i; view()->createFile(std::string("/test/embed/embed1/") + o.str()); } // Sum up all the locations mdFlusher()->synchronize(); size_t numReplicas = countReplicas(fsview()); ASSERT_EQ(numReplicas, 20000); size_t numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 0); ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500); // Unlink replicas for (int i = 100; i < 500; ++i) { std::ostringstream o; o << "file" << i; // Unlink some replicas std::shared_ptr f = c->findFile(o.str()); f->unlinkLocation(f->getLocation(0)); f->unlinkLocation(f->getLocation(0)); view()->updateFileStore(f.get()); } mdFlusher()->synchronize(); numReplicas = countReplicas(fsview()); ASSERT_EQ(numReplicas, 19200); numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 800); std::list file_ids; for (int i = 500; i < 900; ++i) { std::ostringstream o; o << "file" << i; // Unlink some replicas std::shared_ptr f{c->findFile(o.str())}; f->unlinkAllLocations(); c->removeFile(o.str()); f->setContainerId(0); file_ids.push_back(f->getId()); view()->updateFileStore(f.get()); } mdFlusher()->synchronize(); numReplicas = countReplicas(fsview()); ASSERT_EQ(numReplicas, 17200); numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 2800); // Restart shut_down_everything(); numReplicas = countReplicas(fsview()); ASSERT_EQ(numReplicas, 17200); numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 2800); ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500); std::shared_ptr f{ view()->getFile(std::string("/test/embed/embed1/file1"))}; ASSERT_EQ(view()->getUri(f.get()), "/test/embed/embed1/file1"); f->unlinkAllLocations(); mdFlusher()->synchronize(); numReplicas = countReplicas(fsview()); ASSERT_EQ(numReplicas, 17195); numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 2805); f->removeAllLocations(); mdFlusher()->synchronize(); numUnlinked = countUnlinked(fsview()); ASSERT_EQ(numUnlinked, 2800); view()->updateFileStore(f.get()); ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 501); view()->removeFile(f.get()); mdFlusher()->synchronize(); ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500); shut_down_everything(); // Cleanup - remove all files for (int i = 0; i < 1000; ++i) { std::ostringstream o; o << "file" << i; std::list paths; paths.push_back("/test/embed/" + o.str()); paths.push_back("/test/embed/embed1/" + o.str()); paths.push_back("/test/embed/embed2/" + o.str()); paths.push_back("/test/embed/embed3/" + o.str()); for (auto && elem : paths) { // Skip the files that have already been removed if ((elem == "/test/embed/embed1/file1") || (i >= 500 && i < 900 && elem.find("/test/embed/embed2/") == 0)) { continue; } std::shared_ptr file{view()->getFile(elem)}; ASSERT_EQ(view()->getUri(file.get()), elem); view()->unlinkFile(file.get()); file->removeAllLocations(); view()->removeFile(file.get()); } } // Remove the files that were unlinked only for (auto && id : file_ids) { std::shared_ptr file = fileSvc()->getFileMD(id); file->removeAllLocations(); view()->removeFile(file.get()); } for (int i = 0; i < 500; ++i) { std::ostringstream o; o << "noreplicasfile" << i; std::string path = "/test/embed/embed1/" + o.str(); std::shared_ptr file{view()->getFile(path)}; ASSERT_EQ(view()->getUri(file.get()), path); view()->unlinkFile(file.get()); view()->removeFile(file.get()); } // Remove all containers eos::RmrfHelper::nukeDirectory(view(), "/test/"); // Remove the root container std::shared_ptr root{view()->getContainer("/")}; containerSvc()->removeContainer(root.get()); } //------------------------------------------------------------------------------ // Test retrieval of random file ids in a filesystem view //------------------------------------------------------------------------------ TEST_F(FileSystemViewF, RandomFilePicking) { view()->createContainer("/test/", true); for (size_t i = 1; i < 200; i++) { std::shared_ptr file = view()->createFile(SSTR("/test/" << i)); ASSERT_EQ(view()->getUri(file.get()), SSTR("/test/" << i)); ASSERT_EQ(view()->getUriFut(file->getIdentifier()).get(), SSTR("/test/" << i)); // Even files go to fs #1, odd go to #2 if (i % 2 == 0) { file->addLocation(1); } else { file->addLocation(2); } view()->updateFileStore(file.get()); } mdFlusher()->synchronize(); for (size_t i = 0; i < 1000; i++) { eos::IFileMD::id_t randomPick; ASSERT_TRUE(fsview()->getApproximatelyRandomFileInFs(1, randomPick)); ASSERT_TRUE(randomPick % 2 == 0); if (i < 10) { std::cout << "Random file in fs #1: " << randomPick << std::endl; } ASSERT_TRUE(fsview()->getApproximatelyRandomFileInFs(2, randomPick)); ASSERT_TRUE(randomPick % 2 == 1); if (i < 10) { std::cout << "Random file in fs #2: " << randomPick << std::endl; } } eos::IFileMD::id_t randomPick; ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(3, randomPick)); ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(5, randomPick)); ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(4, randomPick)); } //------------------------------------------------------------------------------ // Test file iterator on top of QHash object //------------------------------------------------------------------------------ TEST_F(FileSystemViewF, FileIterator) { std::srand(std::time(0)); std::unordered_set input_set; for (std::uint64_t i = 0ull; i < 50000; ++i) { double frac = std::rand() / (double)RAND_MAX; (void)input_set.insert((uint64_t)(UINT64_MAX * frac)); } // Push the set to QuarkDB qclient::AsyncHandler ah; const std::string key = "set_iter_test"; qclient::QSet set(qcl(), key); for (auto elem : input_set) { set.sadd_async(std::to_string(elem), &ah); } ASSERT_TRUE(ah.Wait()); std::unordered_set result_set; auto iter = std::shared_ptr> (new eos::StreamingFileListIterator(qcl(), key)); for (; (iter && iter->valid()); iter->next()) { result_set.insert(iter->getElement()); } ASSERT_EQ(input_set.size(), result_set.size()); for (auto elem : input_set) { ASSERT_TRUE(result_set.find(elem) != result_set.end()); } qcl().del(key); } //------------------------------------------------------------------------------ // Test file list iterator //------------------------------------------------------------------------------ TEST_F(FileSystemViewF, FileListIterator) { view()->createContainer("/test/", true); eos::IFileMDPtr f1 = view()->createFile("/test/f1"); ASSERT_EQ(f1->getIdentifier(), eos::FileIdentifier(1)); f1->addLocation(1); f1->addLocation(2); f1->addLocation(3); eos::IFileMDPtr f2 = view()->createFile("/test/f2"); ASSERT_EQ(f2->getIdentifier(), eos::FileIdentifier(2)); f2->addLocation(2); eos::IFileMDPtr f3 = view()->createFile("/test/f3"); ASSERT_EQ(f3->getIdentifier(), eos::FileIdentifier(3)); f3->addLocation(2); f3->addLocation(3); eos::IFileMDPtr f4 = view()->createFile("/test/f4"); ASSERT_EQ(f4->getIdentifier(), eos::FileIdentifier(4)); f4->addLocation(4); { auto it = fsview()->getFileList(1); ASSERT_TRUE(it->valid()); ASSERT_EQ(it->getElement(), 1u); it->next(); ASSERT_FALSE(it->valid()); } ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(2), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(3), std::set { 1, 3 })); ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getFileList(3), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(4), std::set { 4 })); shut_down_everything(); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(2), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(3), std::set { 1, 3 })); ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getFileList(3), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(4), std::set { 4 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(2), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(3), std::set { 1, 3 })); ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(3), std::set { 1, 2, 3 })); ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(4), std::set { 4 })); } //------------------------------------------------------------------------------ // Tests targetting FileSystemHandler //------------------------------------------------------------------------------ TEST_F(FileSystemViewF, FileSystemHandler) { // We're only using FileSystemHandler on its own in this test, don't spin // up the rest of the namespace.. std::unique_ptr executor; executor.reset(new folly::IOThreadPoolExecutor(16)); { eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false); ASSERT_EQ(fs1.getRedisKey(), "fsview:1:files"); fs1.insert(eos::FileIdentifier(1)); fs1.insert(eos::FileIdentifier(8)); fs1.insert(eos::FileIdentifier(10)); fs1.insert(eos::FileIdentifier(20)); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20})); ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 20})); ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20, 30})); fs1.erase(eos::FileIdentifier(30)); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20})); fs1.erase(eos::FileIdentifier(20)); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10})); fs1.insert(eos::FileIdentifier(20)); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20})); } shut_down_everything(); // Make sure we pick up on any changes. for (int i = 0; i < 3; i++) { eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20})); ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 20})); ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20, 30})); } // Re-iterate just in case, this time verify contents directly from QDB. qclient::QSet qset(qcl(), eos::RequestBuilder::keyFilesystemFiles(1)); { auto it = qset.getIterator(); ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set { "1", "8", "10", "20" })); } { auto it = qset.getIterator(); ASSERT_FALSE(eos::ns::testing::verifyContents(&it, std::set { "1", "8", "10" })); } { auto it = qset.getIterator(); ASSERT_FALSE(eos::ns::testing::verifyContents(&it, std::set { "1", "8", "10", "20", "30" })); } shut_down_everything(); // Add item, make sure change is reflected in QDB. { eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20})); fs1.insert(eos::FileIdentifier(99)); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20, 99})); mdFlusher()->synchronize(); // Try streaming iterator ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getStreamingFileList(), std::set {1, 8, 10, 20, 99})); } shut_down_everything(); { qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1)); auto it = qset2.getIterator(); ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set { "1", "8", "10", "20", "99" })); } // Nuke filelist { eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set {1, 8, 10, 20, 99})); fs1.nuke(); ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(), std::set { })); } mdFlusher()->synchronize(); // Ensure it's empty in the backend as well { qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1)); auto it = qset2.getIterator(); ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set { })); } } //------------------------------------------------------------------------------ // Tests targetting FileSystemHandler cache clearing functionality //------------------------------------------------------------------------------ TEST_F(FileSystemViewF, FileSystemHandlerCache) { // We're only using FileSystemHandler on its own in this test, don't spin // up the rest of the namespace.. std::unique_ptr executor; executor.reset(new folly::IOThreadPoolExecutor(16)); { eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false, true); ASSERT_EQ(fs1.getRedisKey(), "fsview:1:files"); for (int i = 100; i < 400; ++i) { if (i % 2 == 0) { fs1.insert(eos::FileIdentifier(i)); } } mdFlusher()->synchronize(); ASSERT_EQ(150, fs1.size()); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded, fs1.getCacheStatus()); fs1.ensureContentsLoaded(); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus()); ASSERT_EQ(150, fs1.size()); for (int i = 100; i < 200; ++i) { if (i % 2 == 0) { fs1.erase(eos::FileIdentifier(i)); } } ASSERT_EQ(100, fs1.size()); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus()); fs1.clearCache(std::chrono::seconds(10)); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus()); fs1.mClock.advance(std::chrono::seconds(30)); fs1.clearCache(std::chrono::seconds(10)); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded, fs1.getCacheStatus()); for (int i = 200; i < 300; ++i) { if (i % 2 == 0) { fs1.erase(eos::FileIdentifier(i)); } } mdFlusher()->synchronize(); ASSERT_EQ(50, fs1.size()); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded, fs1.getCacheStatus()); ASSERT_TRUE(fs1.hasFileId(300)); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus()); fs1.clearCache(std::chrono::seconds(60)); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus()); fs1.mClock.advance(std::chrono::seconds(54)); fs1.clearCache(std::chrono::seconds(53)); ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded, fs1.getCacheStatus()); fs1.nuke(); } mdFlusher()->synchronize(); // Ensure it's empty in the backend as well { qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1)); auto it = qset2.getIterator(); ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set { })); } } TEST(SetChangeList, BasicSanity) { eos::IFsView::FileList contents; contents.set_deleted_key(0); contents.set_empty_key(0xffffffffffffffffll); eos::SetChangeList changeList; contents.insert(5); contents.insert(9); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 5, 9 })); changeList.push_back(10); changeList.erase(5); changeList.apply(contents); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 9, 10 })); changeList.clear(); changeList.push_back(20); changeList.clear(); changeList.apply(contents); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 9, 10 })); changeList.push_back(99); changeList.push_back(99); changeList.push_back(12); changeList.push_back(13); changeList.erase(12); changeList.apply(contents); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 9, 10, 13, 99 })); changeList.clear(); changeList.push_back(15); changeList.push_back(16); changeList.erase(10); changeList.apply(contents); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 9, 13, 15, 16, 99 })); changeList.clear(); changeList.push_back(17); changeList.push_back(17); changeList.erase(17); changeList.erase(17); changeList.apply(contents); ASSERT_EQ(changeList.size(), 4u); ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(), std::set { 9, 13, 15, 16, 99 })); }