/************************************************************************ * 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 //! @breif HierarchicalView tests //------------------------------------------------------------------------------ #include "namespace/interface/IContainerMD.hh" #include "namespace/ns_quarkdb/accounting/QuotaStats.hh" #include "namespace/ns_quarkdb/persistency/ContainerMDSvc.hh" #include "namespace/ns_quarkdb/persistency/FileMDSvc.hh" #include "namespace/ns_quarkdb/views/HierarchicalView.hh" #include "namespace/ns_quarkdb/tests/TestUtils.hh" #include "namespace/ns_quarkdb/utils/QuotaRecomputer.hh" #include "namespace/utils/RmrfHelper.hh" #include "namespace/Resolver.hh" #include "namespace/utils/RenameSafetyCheck.hh" #include "common/LayoutId.hh" #include "namespace/utils/BulkNsObjectLocker.hh" #include #include #include #include #include #include #include #include #include #include class HierarchicalViewF : public eos::ns::testing::NsTestsFixture {}; TEST_F(HierarchicalViewF, LoadTest) { std::shared_ptr cont1 = view()->createContainer("/test/embed/embed1", true); std::shared_ptr cont2 = view()->createContainer("/test/embed/embed2", true); std::shared_ptr cont3 = view()->createContainer("/test/embed/embed3", true); std::shared_ptr cont4 = view()->createContainer("/test/embed/embed4", true); std::shared_ptr root = view()->getContainer("/"); std::shared_ptr test = view()->getContainer("/test"); std::shared_ptr embed = view()->getContainer("/test/embed"); ASSERT_THROW(embed->setName("with/slashes"), eos::MDException); ASSERT_TRUE(root != nullptr); ASSERT_TRUE(root->getId() == root->getParentId()); ASSERT_TRUE(test != nullptr); ASSERT_TRUE(test->findContainer("embed") != nullptr); ASSERT_TRUE(embed != nullptr); ASSERT_EQ(root->getId(), 1); ASSERT_NE(test->getId(), 1); ASSERT_NE(embed->getId(), 1); ASSERT_TRUE(embed->findContainer("embed1") != nullptr); ASSERT_TRUE(embed->findContainer("embed2") != nullptr); ASSERT_TRUE(embed->findContainer("embed3") != nullptr); ASSERT_TRUE(cont1->getName() == embed->findContainer("embed1")->getName()); ASSERT_TRUE(cont2->getName() == embed->findContainer("embed2")->getName()); ASSERT_TRUE(cont3->getName() == embed->findContainer("embed3")->getName()); view()->removeContainer("/test/embed/embed2"); ASSERT_TRUE(embed->findContainer("embed2") == nullptr); view()->createFile("/test/embed/file1"); view()->createFile("/test/embed/file2"); view()->createFile("/test/embed/embed1/file1"); view()->createFile("/test/embed/embed1/file2"); view()->createFile("/test/embed/embed1/file3"); std::shared_ptr fileR = view()->createFile("/test/embed/embed1/fileR"); ASSERT_THROW(fileR->setName("has/slashes"), eos::MDException); ASSERT_TRUE(view()->getFile("/test/embed/file1")); ASSERT_TRUE(view()->getFile("/test/embed/file2")); ASSERT_TRUE(view()->getFile("/test/embed/embed1/file1")); ASSERT_TRUE(view()->getFile("/test/embed/embed1/file2")); ASSERT_TRUE(view()->getFile("/test/embed/embed1/file3")); // Rename view()->renameContainer(cont4.get(), "embed4.renamed"); ASSERT_TRUE(cont4->getName() == "embed4.renamed"); ASSERT_THROW(view()->renameContainer(cont4.get(), "embed1"), eos::MDException); ASSERT_THROW(view()->renameContainer(cont4.get(), "embed1/asd"), eos::MDException); view()->getContainer("/test/embed/embed4.renamed"); view()->renameFile(fileR.get(), "fileR.renamed"); ASSERT_TRUE(fileR->getName() == "fileR.renamed"); ASSERT_THROW(view()->renameFile(fileR.get(), "file1"), eos::MDException); ASSERT_THROW(view()->renameFile(fileR.get(), "file1/asd"), eos::MDException); view()->getFile("/test/embed/embed1/fileR.renamed"); ASSERT_THROW(view()->renameContainer(root.get(), "rename"), eos::MDException); // Test the "reverse" lookup std::shared_ptr file = view()->getFile("/test/embed/embed1/file3"); std::shared_ptr container = view()->getContainer("/test/embed/embed1"); ASSERT_EQ(view()->getUri(container.get()), "/test/embed/embed1/"); ASSERT_EQ(view()->getUriFut(container->getIdentifier()).get(), "/test/embed/embed1/"); ASSERT_EQ(view()->getUri(file.get()), "/test/embed/embed1/file3"); ASSERT_EQ(view()->getUriFut(file->getIdentifier()).get(), "/test/embed/embed1/file3"); ASSERT_THROW(view()->getUri((eos::IFileMD*)nullptr), eos::MDException); ASSERT_THROW(view()->getUriFut(eos::FileIdentifier(9999999)).get(), eos::MDException); std::shared_ptr toBeDeleted = view()->getFile("/test/embed/embed1/file2"); toBeDeleted->addLocation(12); // This should not succeed since the file should have a replica ASSERT_THROW(view()->removeFile(toBeDeleted.get()), eos::MDException); // We unlink the file - at this point the file should not be attached to the // hierarchy but should still be accessible by id and thus the md pointer // should stay valid view()->unlinkFile("/test/embed/embed1/file2"); ASSERT_THROW(view()->getFile("/test/embed/embed1/file2"), eos::MDException); ASSERT_TRUE(cont1->findFile("file2") == nullptr); // We remove the replicas and the file but we need to reload the toBeDeleted // pointer eos::IFileMD::id_t id = toBeDeleted->getId(); toBeDeleted = fileSvc()->getFileMD(id); toBeDeleted->clearUnlinkedLocations(); view()->removeFile(toBeDeleted.get()); ASSERT_THROW(fileSvc()->getFileMD(id), eos::MDException); shut_down_everything(); ASSERT_TRUE(view()->getContainer("/")); ASSERT_TRUE(view()->getContainer("/test")); ASSERT_TRUE(view()->getContainer("/test/embed")); ASSERT_TRUE(view()->getContainer("/test/embed/embed1")); ASSERT_TRUE(view()->getFile("/test/embed/file1")); ASSERT_TRUE(view()->getFile("/test/embed/file2")); ASSERT_TRUE(view()->getFile("/test/embed/embed1/file1")); ASSERT_TRUE(view()->getFile("/test/embed/embed1/file3")); view()->getContainer("/test/embed/embed4.renamed"); view()->getFile("/test/embed/embed1/fileR.renamed"); // Cleanup // Unlink files - need to do it in this order since the unlink removes the // file from the container and then getFile by path won't work anymore std::shared_ptr file1 = view()->getFile("/test/embed/file1"); std::shared_ptr file2 = view()->getFile("/test/embed/file2"); std::shared_ptr file11 = view()->getFile("/test/embed/embed1/file1"); std::shared_ptr file13 = view()->getFile("/test/embed/embed1/file3"); view()->unlinkFile("/test/embed/file1"); view()->unlinkFile("/test/embed/file2"); view()->unlinkFile("/test/embed/embed1/file1"); view()->unlinkFile("/test/embed/embed1/file3"); view()->unlinkFile("/test/embed/embed1/fileR.renamed"); // Remove files view()->removeFile(fileSvc()->getFileMD(file1->getId()).get()); view()->removeFile(fileSvc()->getFileMD(file2->getId()).get()); view()->removeFile(fileSvc()->getFileMD(file11->getId()).get()); view()->removeFile(fileSvc()->getFileMD(file13->getId()).get()); view()->removeFile(fileSvc()->getFileMD(fileR->getId()).get()); // Remove all containers eos::RmrfHelper::nukeDirectory(view(), "/test/"); } //------------------------------------------------------------------------------ // File size mapping function //------------------------------------------------------------------------------ static uint64_t mapSize(const eos::IFileMD* file) { eos::IFileMD::layoutId_t lid = file->getLayoutId(); if (lid > 3) { eos::MDException e(ENOENT); e.getMessage() << "Location does not exist" << std::endl; throw (e); } return lid * file->getSize(); } //------------------------------------------------------------------------------ // Create files at given path //------------------------------------------------------------------------------ static void createFiles(const std::string& path, eos::IView* view, std::map* users, std::map* groups) { eos::IQuotaNode* node = view->getQuotaNode(view->getContainer(path).get()); for (int i = 0; i < 1000; ++i) { std::ostringstream p; p << path << "file" << i; std::shared_ptr file{view->createFile(p.str())}; file->setCUid(random() % 10 + 1); file->setCGid(random() % 3 + 1); file->setSize(random() % 1000000 + 1); file->setLayoutId(random() % 3 + 1); view->updateFileStore(file.get()); node->addFile(file.get()); uint64_t size = mapSize(file.get()); eos::QuotaNodeCore::UsageInfo& user = (*users)[file->getCUid()]; eos::QuotaNodeCore::UsageInfo& group = (*groups)[file->getCGid()]; user.space += file->getSize(); user.physicalSpace += size; user.files++; group.space += file->getSize(); group.physicalSpace += size; group.files++; } } TEST_F(HierarchicalViewF, ZeroSizedFilenames) { eos::IContainerMDPtr cont1 = view()->createContainer("/test/dir1", true); eos::IContainerMDPtr cont2 = view()->createContainer("/dir2", true); eos::IFileMDPtr file1 = view()->createFile("/file1", true); file1->setName(""); ASSERT_THROW(cont1->addFile(file1.get()), eos::MDException); ASSERT_THROW(cont2->setName(""); , eos::MDException); } //------------------------------------------------------------------------------ // Test namespace resolver based on (path, cid, cxid) //------------------------------------------------------------------------------ TEST_F(HierarchicalViewF, Resolver) { // Make a lot of containers for (size_t i = 0; i < 50; i++) { view()->createContainer(SSTR("/dir" << i), true); } eos::ContainerSpecificationProto spec; ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException); spec.set_path("/dir49"); eos::IContainerMDPtr cont = eos::Resolver::resolveContainer(view(), spec); ASSERT_EQ(cont->getName(), "dir49"); spec.set_cid("48"); cont = eos::Resolver::resolveContainer(view(), spec); ASSERT_EQ(cont->getName(), "dir46"); spec.set_cxid("30"); cont = eos::Resolver::resolveContainer(view(), spec); ASSERT_EQ(cont->getName(), "dir46"); spec.set_path("/chicken"); ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException); spec.set_cid("chicken chicken"); ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException); spec.set_cxid("chicken"); ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException); } TEST_F(HierarchicalViewF, QuotaTest) { srandom(time(nullptr)); // Initialize the system setSizeMapper(mapSize); // Create some structures, insert quota nodes and test their correctness std::shared_ptr cont1{ view()->createContainer("/test/embed/embed1", true)}; std::shared_ptr cont2{ view()->createContainer("/test/embed/embed2", true)}; std::shared_ptr cont3{ view()->createContainer("/test/embed/embed3", true)}; std::shared_ptr cont4{view()->getContainer("/test/embed")}; std::shared_ptr cont5{view()->getContainer("/test")}; eos::IQuotaNode* qnCreated1 = view()->registerQuotaNode(cont1.get()); eos::IQuotaNode* qnCreated2 = view()->registerQuotaNode(cont3.get()); eos::IQuotaNode* qnCreated3 = view()->registerQuotaNode(cont5.get()); ASSERT_THROW(view()->registerQuotaNode(cont1.get()), eos::MDException); ASSERT_TRUE(qnCreated1); ASSERT_TRUE(qnCreated2); ASSERT_TRUE(qnCreated3); eos::IQuotaNode* qn1 = view()->getQuotaNode(cont1.get()); eos::IQuotaNode* qn2 = view()->getQuotaNode(cont2.get()); eos::IQuotaNode* qn3 = view()->getQuotaNode(cont3.get()); eos::IQuotaNode* qn4 = view()->getQuotaNode(cont4.get()); eos::IQuotaNode* qn5 = view()->getQuotaNode(cont5.get()); ASSERT_TRUE(qn1); ASSERT_TRUE(qn2); ASSERT_TRUE(qn3); ASSERT_TRUE(qn4); ASSERT_TRUE(qn5); ASSERT_TRUE(qn2 == qn5); ASSERT_TRUE(qn4 == qn5); ASSERT_TRUE(qn1 != qn5); ASSERT_TRUE(qn3 != qn5); ASSERT_TRUE(qn3 != qn2); // Create some files std::map users1; std::map groups1; std::string path1 = "/test/embed/embed1/"; createFiles(path1, view(), &users1, &groups1); std::map users2; std::map groups2; std::string path2 = "/test/embed/embed2/"; createFiles(path2, view(), &users2, &groups2); std::map users3; std::map groups3; std::string path3 = "/test/embed/embed3/"; createFiles(path3, view(), &users3, &groups3); // Verify correctness eos::IQuotaNode* node1 = view()->getQuotaNode(view()->getContainer( path1).get()); eos::IQuotaNode* node2 = view()->getQuotaNode(view()->getContainer( path2).get()); for (int i = 1; i <= 10; ++i) { ASSERT_EQ(node1->getPhysicalSpaceByUser(i), users1[i].physicalSpace); ASSERT_EQ(node2->getPhysicalSpaceByUser(i), users2[i].physicalSpace); ASSERT_EQ(node1->getUsedSpaceByUser(i), users1[i].space); ASSERT_EQ(node2->getUsedSpaceByUser(i), users2[i].space); ASSERT_EQ(node1->getNumFilesByUser(i), users1[i].files); ASSERT_EQ(node2->getNumFilesByUser(i), users2[i].files); } for (int i = 1; i <= 3; ++i) { ASSERT_EQ(node1->getPhysicalSpaceByGroup(i), groups1[i].physicalSpace); ASSERT_EQ(node2->getPhysicalSpaceByGroup(i), groups2[i].physicalSpace); ASSERT_EQ(node1->getUsedSpaceByGroup(i), groups1[i].space); ASSERT_EQ(node2->getUsedSpaceByGroup(i), groups2[i].space); ASSERT_EQ(node1->getNumFilesByGroup(i), groups1[i].files); ASSERT_EQ(node2->getNumFilesByGroup(i), groups2[i].files); } // Restart and check if the quota stats are reloaded correctly shut_down_everything(); node1 = view()->getQuotaNode(view()->getContainer(path1).get()); node2 = view()->getQuotaNode(view()->getContainer(path2).get()); ASSERT_TRUE(node1); ASSERT_TRUE(node2); for (int i = 1; i <= 10; ++i) { ASSERT_EQ(node1->getPhysicalSpaceByUser(i), users1[i].physicalSpace); ASSERT_EQ(node2->getPhysicalSpaceByUser(i), users2[i].physicalSpace); ASSERT_EQ(node1->getUsedSpaceByUser(i), users1[i].space); ASSERT_EQ(node2->getUsedSpaceByUser(i), users2[i].space); ASSERT_EQ(node1->getNumFilesByUser(i), users1[i].files); ASSERT_EQ(node2->getNumFilesByUser(i), users2[i].files); } for (int i = 1; i <= 3; ++i) { ASSERT_EQ(node1->getPhysicalSpaceByGroup(i), groups1[i].physicalSpace); ASSERT_EQ(node2->getPhysicalSpaceByGroup(i), groups2[i].physicalSpace); ASSERT_EQ(node1->getUsedSpaceByGroup(i), groups1[i].space); ASSERT_EQ(node2->getUsedSpaceByGroup(i), groups2[i].space); ASSERT_EQ(node1->getNumFilesByGroup(i), groups1[i].files); ASSERT_EQ(node2->getNumFilesByGroup(i), groups2[i].files); } // Remove the quota nodes on /test/embed/embed1 and /dest/embed/embed2 // and check if the quota on /test has been updated eos::IQuotaNode* parentNode = nullptr; parentNode = view()->getQuotaNode(view()->getContainer("/test").get()); view()->removeQuotaNode(view()->getContainer(path1).get()); for (int i = 1; i <= 10; ++i) { ASSERT_EQ(parentNode->getPhysicalSpaceByUser(i), users1[i].physicalSpace + users2[i].physicalSpace); ASSERT_EQ(parentNode->getUsedSpaceByUser(i), users1[i].space + users2[i].space); ASSERT_EQ(parentNode->getNumFilesByUser(i), users1[i].files + users2[i].files); } for (int i = 1; i <= 3; ++i) { ASSERT_EQ(parentNode->getPhysicalSpaceByGroup(i), groups1[i].physicalSpace + groups2[i].physicalSpace); ASSERT_EQ(parentNode->getUsedSpaceByGroup(i), groups1[i].space + groups2[i].space); ASSERT_EQ(parentNode->getNumFilesByGroup(i), groups1[i].files + groups2[i].files); } view()->removeQuotaNode(view()->getContainer(path3).get()); ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path3).get()), eos::MDException); for (int i = 1; i <= 10; ++i) { ASSERT_EQ(parentNode->getPhysicalSpaceByUser(i), users1[i].physicalSpace + users2[i].physicalSpace + users3[i].physicalSpace); ASSERT_EQ(parentNode->getUsedSpaceByUser(i), users1[i].space + users2[i].space + users3[i].space); ASSERT_EQ(parentNode->getNumFilesByUser(i), users1[i].files + users2[i].files + users3[i].files); } for (int i = 1; i <= 3; ++i) { ASSERT_EQ(parentNode->getPhysicalSpaceByGroup(i), groups1[i].physicalSpace + groups2[i].physicalSpace + groups3[i].physicalSpace); ASSERT_EQ(parentNode->getUsedSpaceByGroup(i), groups1[i].space + groups2[i].space + groups3[i].space); ASSERT_EQ(parentNode->getNumFilesByGroup(i), groups1[i].files + groups2[i].files + groups3[i].files); } // Clean up // Remove all the quota nodes ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path1).get()), eos::MDException); ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path2).get()), eos::MDException); ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path3).get()), eos::MDException); ASSERT_THROW(view()->removeQuotaNode(view()->getContainer("/test/embed").get()), eos::MDException); view()->removeQuotaNode(cont5.get()); // Remove all the files std::list paths{path1, path2, path3}; for (auto && path_elem : paths) { for (int i = 0; i < 1000; ++i) { std::ostringstream p; p << path_elem << "file" << i; std::shared_ptr file{view()->getFile(p.str())}; view()->unlinkFile(p.str()); view()->removeFile(fileSvc()->getFileMD(file->getId()).get()); } } // Remove all containers ASSERT_NO_THROW(eos::RmrfHelper::nukeDirectory(view(), "/test/")); // Remove the root container std::shared_ptr root{view()->getContainer("/")}; ASSERT_NO_THROW(containerSvc()->removeContainer(root.get())); ASSERT_NO_THROW(view()->finalize()); } TEST_F(HierarchicalViewF, LostContainerTest) { std::shared_ptr cont1 = view()->createContainer("/test/embed/embed1", true); std::shared_ptr cont2 = view()->createContainer("/test/embed/embed2", true); std::shared_ptr cont3 = view()->createContainer("/test/embed/embed3", true); std::shared_ptr cont4 = view()->createContainer("/test/embed/embed1/embedembed", true); std::shared_ptr cont5 = view()->createContainer("/test/embed/embed3.conflict", true); // Create some files for (int i = 0; i < 1000; ++i) { std::ostringstream s1; s1 << "/test/embed/embed1/file" << i; std::ostringstream s2; s2 << "/test/embed/embed2/file" << i; std::ostringstream s3; s3 << "/test/embed/embed3/file" << i; std::ostringstream s4; s4 << "/test/embed/embed1/embedembed/file" << i; std::ostringstream s5; s5 << "/test/embed/embed3.conflict/file" << i; std::ostringstream s6; s6 << "/test/embed/embed2/conflict_file" << i; eos::IFileMDPtr embed1F = view()->createFile(s1.str()); ASSERT_EQ(view()->getParentContainer(embed1F.get()).get(), cont1); view()->createFile(s2.str()); view()->createFile(s3.str()); view()->createFile(s4.str()); view()->createFile(s5.str()); view()->createFile(s6.str()); std::shared_ptr file = view()->getFile(s6.str()); if (i != 0) { ASSERT_THROW(view()->renameFile(file.get(), "conflict_file"), eos::MDException); } else { view()->renameFile(file.get(), "conflict_file"); } } // Trying to remove a non-empty container should result in an exception ASSERT_THROW(view()->getContainerMDSvc()->removeContainer(cont1.get()), eos::MDException); // Trying to rename a container to an already existing one should result in // an exception ASSERT_NO_THROW(cont5->setName("embed3")); // Cleanup for (int i = 0; i < 1000; ++i) { std::ostringstream s1; s1 << "/test/embed/embed1/file" << i; std::ostringstream s2; s2 << "/test/embed/embed2/file" << i; std::ostringstream s3; s3 << "/test/embed/embed3/file" << i; std::ostringstream s4; s4 << "/test/embed/embed1/embedembed/file" << i; std::ostringstream s5; s5 << "/test/embed/embed3.conflict/file" << i; std::ostringstream s6; s6 << "/test/embed/embed2/conflict_file" << i; std::list paths{s1.str(), s2.str(), s3.str(), s4.str(), s5.str()}; if (i != 0) { paths.insert(paths.end(), s6.str()); } for (auto && elem : paths) { std::shared_ptr file = view()->getFile(elem); view()->unlinkFile(elem); view()->removeFile(fileSvc()->getFileMD(file->getId()).get()); } } // Remove the conflict_file std::string path = "test/embed/embed2/conflict_file"; std::shared_ptr file = view()->getFile(path); view()->unlinkFile(path); view()->removeFile(fileSvc()->getFileMD(file->getId()).get()); // Remove all containers // TODO(gbitzes): Something wrong is here, this should succeed, investigate. // eos::RmrfHelper::nukeDirectory(view(), "/test/"); } TEST_F(HierarchicalViewF, RenameDirectoryAsSubdirOfItself) { std::shared_ptr cont1 = view()->createContainer("/eos/dev/my-dir", true); std::shared_ptr cont2 = view()->createContainer("/eos/dev/my-dir/subdir1", true); std::shared_ptr cont3 = view()->createContainer("/eos/dev/my-dir/subdir1/subdir2", true); ASSERT_TRUE(eos::isSafeToRename(view(), cont3.get(), cont1.get())); ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont3.get())); ASSERT_TRUE(eos::isSafeToRename(view(), cont2.get(), cont1.get())); // non-sensical to do, but safe (no-op) ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont2.get())); } TEST_F(HierarchicalViewF, AddFileWithConflicts) { eos::IContainerMDPtr cont1 = view()->createContainer("/test/dir1", true); view()->createContainer("/test/dir1/dir2", true); eos::IContainerMDPtr cont2 = view()->createContainer("/dir1", true); eos::IFileMDPtr file1 = view()->createFile("/test/dir1/file1", true); eos::IFileMDPtr file2 = view()->createFile("/file1", true); ASSERT_THROW(cont1->addFile(file2.get()), eos::MDException); // conflicts with file file2->setName("dir2"); ASSERT_THROW(cont1->addFile(file2.get()), eos::MDException); // conflicts with directory cont1->addFile(file1.get()); // conflicts with itself, thus, no conflict } TEST_F(HierarchicalViewF, AddContainerWithConflicts) { eos::IContainerMDPtr cont1 = view()->createContainer("/test/", true); eos::IContainerMDPtr cont4 = view()->createContainer("/test/dir1", true); eos::IContainerMDPtr cont2 = view()->createContainer("/dir1", true); ASSERT_THROW(cont1->addContainer(cont2.get()), eos::MDException); // conflicts with container view()->createFile("/test/file1", true); eos::IContainerMDPtr cont3 = view()->createContainer("/file1", true); ASSERT_THROW(cont1->addContainer(cont3.get()), eos::MDException); // conflicts with file cont1->addContainer(cont4.get()); // conflicts with itself, thus, no conflict } TEST_F(HierarchicalViewF, QuotaRecomputation) { eos::IContainerMDPtr quota1 = view()->createContainer("/quota1", true); eos::IContainerMDPtr quota2 = view()->createContainer("/quota2", true); eos::IContainerMDPtr quota3 = view()->createContainer("/quota1/quota3", true); eos::IContainerMDPtr notquota1 = view()->createContainer("/not-a-quota", true); eos::IContainerMDPtr notquota2 = view()->createContainer("/quota1/not-a-quota-either", true); containerSvc()->updateStore(quota1.get()); containerSvc()->updateStore(quota2.get()); containerSvc()->updateStore(quota3.get()); unsigned long layoutId = eos::common::LayoutId::GetId( eos::common::LayoutId::kReplica, eos::common::LayoutId::kMD5, 2, eos::common::LayoutId::k4k); for (size_t i = 0; i < 10; i++) { eos::IFileMDPtr file = view()->createFile(SSTR("/quota1/f" << i), true); file->setSize(1337); file->setLayoutId(layoutId); file->setCUid(i % 4); file->setCGid(i % 2); fileSvc()->updateStore(file.get()); } layoutId = eos::common::LayoutId::GetId( eos::common::LayoutId::kReplica, eos::common::LayoutId::kMD5, 3, eos::common::LayoutId::k4k); for (size_t i = 0; i < 15; i++) { eos::IFileMDPtr file = view()->createFile(SSTR("/quota1/quota3/f" << i), true); file->setSize(1338); file->setLayoutId(layoutId); file->setCUid(100); file->setCGid(200); fileSvc()->updateStore(file.get()); } layoutId = eos::common::LayoutId::GetId( eos::common::LayoutId::kReplica, eos::common::LayoutId::kMD5, 5, eos::common::LayoutId::k4k); for (size_t i = 0; i < 17; i++) { eos::IFileMDPtr file = view()->createFile(SSTR("/quota2/f" << i), true); file->setSize(133); file->setLayoutId(layoutId); file->setCUid(i); file->setCGid(9000); fileSvc()->updateStore(file.get()); } mdFlusher()->synchronize(); eos::QuotaNodeCore qnc; eos::QuotaRecomputer recomputer(&(qcl()), executor()); // Simple, non-nested case first: quota2 eos::IQuotaNode* qn2 = view()->registerQuotaNode(quota2.get()); ASSERT_NE(qn2, nullptr); eos::MDStatus status = recomputer.recompute(view()->getUri(quota2.get()), quota2->getId(), qnc); ASSERT_TRUE(status.ok()); ASSERT_EQ(status.getErrno(), 0); ASSERT_EQ(status.getError(), ""); for (size_t i = 0; i < 17; i++) { ASSERT_EQ(qnc.getUsedSpaceByUser(i), 133); ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 133 * 5); ASSERT_EQ(qnc.getNumFilesByUser(i), 1); ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 0); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 0); ASSERT_EQ(qnc.getNumFilesByGroup(i), 0); } ASSERT_EQ(qnc.getUsedSpaceByGroup(9000), 17 * 133); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(9000), 17 * 133 * 5); ASSERT_EQ(qnc.getNumFilesByGroup(9000), 17); // quota1 + quota3 eos::IQuotaNode* qn1p3 = view()->registerQuotaNode(quota1.get()); ASSERT_NE(qn1p3, nullptr); status = recomputer.recompute(view()->getUri(quota1.get()), quota1->getId(), qnc); ASSERT_TRUE(status.ok()); ASSERT_EQ(status.getErrno(), 0); ASSERT_EQ(status.getError(), ""); // uid0 and uid1 have 3 files each for (size_t i = 0; i < 2; i++) { ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 3); ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 3 * 2); ASSERT_EQ(qnc.getNumFilesByUser(i), 3); } // uid2 and uid3 and 2 files each for (size_t i = 2; i < 4; i++) { ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 2); ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 2 * 2); ASSERT_EQ(qnc.getNumFilesByUser(i), 2); } // gid0 and gid1 have 5 each for (size_t i = 0; i < 2; i++) { ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 1337 * 5); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 1337 * 2 * 5); ASSERT_EQ(qnc.getNumFilesByGroup(i), 5); } ASSERT_EQ(qnc.getUsedSpaceByUser(100), 1338 * 15); ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 1338 * 15 * 3); ASSERT_EQ(qnc.getNumFilesByUser(100), 15); ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 1338 * 15); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 1338 * 15 * 3); ASSERT_EQ(qnc.getNumFilesByGroup(200), 15); // register quota3, measure eos::IQuotaNode* qn3 = view()->registerQuotaNode(quota3.get()); ASSERT_NE(qn3, nullptr); status = recomputer.recompute(view()->getUri(quota3.get()), quota3->getId(), qnc); ASSERT_TRUE(status.ok()); ASSERT_EQ(status.getErrno(), 0); ASSERT_EQ(status.getError(), ""); ASSERT_EQ(qnc.getUsedSpaceByUser(100), 1338 * 15); ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 1338 * 15 * 3); ASSERT_EQ(qnc.getNumFilesByUser(100), 15); ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 1338 * 15); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 1338 * 15 * 3); ASSERT_EQ(qnc.getNumFilesByGroup(200), 15); // measure quota1 _on its own_, without embedded quota3 status = recomputer.recompute(view()->getUri(quota1.get()), quota1->getId(), qnc); ASSERT_TRUE(status.ok()); ASSERT_EQ(status.getErrno(), 0); ASSERT_EQ(status.getError(), ""); // uid0 and uid1 have 3 files each for (size_t i = 0; i < 2; i++) { ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 3); ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 3 * 2); ASSERT_EQ(qnc.getNumFilesByUser(i), 3); } // uid2 and uid3 and 2 files each for (size_t i = 2; i < 4; i++) { ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 2); ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 2 * 2); ASSERT_EQ(qnc.getNumFilesByUser(i), 2); } // gid0 and gid1 have 5 each for (size_t i = 0; i < 2; i++) { ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 1337 * 5); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 1337 * 2 * 5); ASSERT_EQ(qnc.getNumFilesByGroup(i), 5); } ASSERT_EQ(qnc.getUsedSpaceByUser(100), 0); ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 0); ASSERT_EQ(qnc.getNumFilesByUser(100), 0); ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 0); ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 0); ASSERT_EQ(qnc.getNumFilesByGroup(200), 0); } TEST_F(HierarchicalViewF, CustomContainerId) { eos::IContainerMDPtr c32 = view()->createContainer("/c32", false, 32); ASSERT_EQ(c32->getId(), 32); eos::IContainerMDPtr root = view()->getContainer("/"); ASSERT_EQ(root->getId(), 1); eos::IContainerMDPtr child = root->findContainer("c32"); ASSERT_EQ(child.get(), c32.get()); eos::IContainerMDPtr c33 = view()->createContainer("/c33", true); ASSERT_EQ(c33->getId(), 33); } TEST_F(HierarchicalViewF, CustomFileId) { eos::IFileMDPtr f999 = view()->createFile("/f999", 5, 5, 999); ASSERT_EQ(f999->getId(), 999); eos::IFileMDPtr f1000 = view()->createFile("/f1000", 0, 0, 0); ASSERT_EQ(f1000->getId(), 1000); } //------------------------------------------------------------------------------ // Tests targetting BulkNsObjectLocker //------------------------------------------------------------------------------ TEST_F(HierarchicalViewF, BulkNsObjectLocker) { { auto container = view()->createContainer("/test/", true); auto container2 = view()->createContainer("/test/d1", true); eos::BulkNsObjectLocker locker; locker.add(container2); locker.add(container); auto locks = locker.lockAll(); // The order of the locks should be by ascending order of the container identifier ASSERT_EQ(2, locks.size()); ASSERT_EQ("test", locks[0]->getUnderlyingPtr()->getName()); ASSERT_EQ("d1", locks[1]->getUnderlyingPtr()->getName()); } { auto file1 = view()->createFile("/test/f1"); auto file2 = view()->createFile("/test/d1/f2"); eos::BulkNsObjectLocker locker; locker.add(file2); locker.add(file1); auto locks = locker.lockAll(); ASSERT_EQ(2, locks.size()); ASSERT_EQ("f1", locks[0]->getUnderlyingPtr()->getName()); ASSERT_EQ("f2", locks[1]->getUnderlyingPtr()->getName()); } } TEST_F(HierarchicalViewF, fileMDLockedSetSize) { auto container = view()->createContainer("/test/", true); eos::IFileMDPtr f1 = view()->createFile("/test/f1"); std::vector workers; f1->setSize(100); { //10 threads, each of them running a loop for (int i = 0; i < 10; ++i) { workers.push_back(std::thread([this, i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { if (i % 2 == 0) { f1->setSize(f1->getSize() + 1); } else { f1->setSize(f1->getSize() - 1); } } view()->updateFileStore(f1.get()); })); } } std::for_each(workers.begin(), workers.end(), [](std::thread & t) { t.join(); }); //We sleep 6 seconds the time for the ContainerAccountingThread to take into account the size change of the file ::sleep(6); ASSERT_EQ(100, f1->getSize()); ASSERT_EQ(100, container->getTreeSize()); } TEST_F(HierarchicalViewF, fileMDLockedClone) { view()->createContainer("/test/", true); eos::IFileMDPtr f1 = view()->createFile("/test/f1"); ASSERT_EQ(f1->getIdentifier(), eos::FileIdentifier(1)); eos::IFileMDPtr f2; { eos::IFileMD::IFileMDWriteLocker fileMDLocked(f1); f2.reset(fileMDLocked->clone()); eos::IFileMD::IFileMDReadLocker file2MDLocked(f2); ASSERT_EQ(f1->getIdentifier(), f2->getIdentifier()); } ASSERT_EQ(eos::FileIdentifier(1), f1->getIdentifier()); ASSERT_EQ(f1->getIdentifier(), f2->getIdentifier()); } TEST_F(HierarchicalViewF, fileMDLockedLocation) { view()->createContainer("/test/", true); eos::IFileMDPtr f1 = view()->createFile("/test/f1"); { eos::IFileMD::IFileMDWriteLocker lock(f1); f1->addLocation(1); ASSERT_EQ(1, f1->getLocation(0)); } std::vector addLocationWorkers; { //10 threads, each one adds a location for (int i = 0; i < 10; ++i) { addLocationWorkers.push_back(std::thread([this, i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { f1->addLocation((i * 10) + j); } view()->updateFileStore(f1.get()); })); } } std::for_each(addLocationWorkers.begin(), addLocationWorkers.end(), [](std::thread & t) { t.join(); }); std::vector hasLocationWorkers; { for (int i = 0; i < 10; ++i) { hasLocationWorkers.push_back(std::thread([i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { ASSERT_TRUE(f1->hasLocation((i * 10) + j)); } })); } } std::for_each(hasLocationWorkers.begin(), hasLocationWorkers.end(), [](std::thread & t) { t.join(); }); { eos::IFileMD::IFileMDWriteLocker lock(f1); ASSERT_EQ(100, f1->getNumLocation()); auto locations = f1->getLocations(); ASSERT_EQ(100, locations.size()); } std::vector removeLocationWorkers; { //10 threads, each one adds a location for (int i = 0; i < 10; ++i) { removeLocationWorkers.push_back(std::thread([this, i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { f1->unlinkLocation((i * 10) + j); f1->removeLocation((i * 10) + j); } view()->updateFileStore(f1.get()); })); } } std::for_each(removeLocationWorkers.begin(), removeLocationWorkers.end(), [](std::thread & t) { t.join(); }); ASSERT_EQ(0, f1->getNumLocation()); //Add again 100 locations addLocationWorkers.clear(); for (int i = 0; i < 10; ++i) { addLocationWorkers.push_back(std::thread([this, i, &f1]() { for (int j = 0; j < 10; ++j) { f1->addLocation((i * 10) + j); view()->updateFileStore(f1.get()); } })); } std::for_each(addLocationWorkers.begin(), addLocationWorkers.end(), [](std::thread & t) { t.join(); }); //Try the removeAllLocation { eos::IFileMD::IFileMDWriteLocker lock(f1); f1->unlinkAllLocations(); f1->removeAllLocations(); view()->updateFileStore(f1.get()); } ASSERT_EQ(0, f1->getNumLocation()); addLocationWorkers.clear(); for (int i = 0; i < 10; ++i) { addLocationWorkers.push_back(std::thread([this, i, &f1]() { for (int j = 0; j < 10; ++j) { f1->addLocation((i * 10) + j); } view()->updateFileStore(f1.get()); })); } std::for_each(addLocationWorkers.begin(), addLocationWorkers.end(), [](std::thread & t) { t.join(); }); f1->unlinkLocation(0); ASSERT_EQ(1, f1->getUnlinkedLocations().size()); ASSERT_EQ(1, f1->getNumUnlinkedLocation()); f1->clearUnlinkedLocations(); view()->updateFileStore(f1.get()); ASSERT_EQ(0, f1->getNumUnlinkedLocation()); } TEST_F(HierarchicalViewF, fileMDLockedRemoveLocation) { view()->createContainer("/test/", true); eos::IFileMDPtr f1 = view()->createFile("/test/f1"); std::vector addLocationWorkers; std::vector removeLocationWorkers; for (int i = 0; i < 10; ++i) { addLocationWorkers.push_back(std::thread([this, i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { f1->addLocation((i * 10) + j); view()->updateFileStore(f1.get()); } })); } std::for_each(addLocationWorkers.begin(), addLocationWorkers.end(), [](std::thread & t) { t.join(); }); for (int i = 0; i < 10; ++i) { removeLocationWorkers.push_back(std::thread([this, i, &f1]() { eos::IFileMD::IFileMDWriteLocker lock(f1); for (int j = 0; j < 10; ++j) { f1->unlinkLocation((i * 10) + j); f1->removeLocation((i * 10) + j); view()->updateFileStore(f1.get()); } })); } std::for_each(removeLocationWorkers.begin(), removeLocationWorkers.end(), [](std::thread & t) { t.join(); }); ASSERT_EQ(0, f1->getNumLocation()); } TEST_F(HierarchicalViewF, containerMDFindItem) { const int nbLoops = 10; eos::IContainerMDPtr testCont = view()->createContainer("/test/", true); for (int i = 0; i < nbLoops; ++i) { std::stringstream fileName; std::stringstream contName; fileName << "/test/f" << i; contName << "/test/c" << i; view()->createFile(fileName.str()); view()->createContainer(contName.str()); } std::vector workers; for (int i = 1; i < nbLoops; ++i) { workers.push_back(std::thread([i, testCont]() { { std::stringstream fileName; std::stringstream contName; fileName << "f" << i; contName << "c" << i; eos::IContainerMD::IContainerMDReadLocker containerMDLocker(testCont); { auto contOrFile = testCont->findItem(fileName.str()).get(); ASSERT_TRUE(contOrFile.file != nullptr); } { auto contOrFile = testCont->findItem(contName.str()).get(); ASSERT_TRUE(contOrFile.container != nullptr); } } })); } { eos::IContainerMD::IContainerMDReadLocker containerMDLocker(testCont); { auto contOrFile = testCont->findItem("f0").get(); ASSERT_TRUE(contOrFile.file != nullptr); } { auto contOrFile = testCont->findItem("c0").get(); ASSERT_TRUE(contOrFile.container != nullptr); } } for (auto& t : workers) { t.join(); } } TEST_F(HierarchicalViewF, containerMDAddContainerThenRemove) { auto rootContainer = view()->createContainer("/root/", true); auto rootContainerID = rootContainer->getId(); eos::IContainerMD::IContainerMDWriteLocker rootLocker(rootContainer); auto testContainer = view()->createContainer("/test/", true); auto testContainerID = testContainer->getId(); rootContainer->addContainer(testContainer.get()); view()->updateContainerStore(rootContainer.get()); ASSERT_EQ(1, rootContainer->getNumContainers()); ASSERT_EQ(rootContainerID, testContainer->getParentId()); ASSERT_EQ(testContainerID, testContainer->getIdentifier().getUnderlyingUInt64()); rootContainer->removeContainer("test"); view()->updateContainerStore(rootContainer.get()); ASSERT_EQ(0, rootContainer->getNumContainers()); } TEST_F(HierarchicalViewF, containerMDaddFileThenRemove) { auto rootContainer = view()->createContainer("/root/", true); eos::IContainerMD::IContainerMDWriteLocker rootLocker(rootContainer); auto testFile = view()->createFile("/root/test"); rootContainer->addFile(testFile.get()); ASSERT_EQ(1, rootContainer->getNumFiles()); rootContainer->removeFile("test"); ASSERT_EQ(0, rootContainer->getNumFiles()); } TEST_F(HierarchicalViewF, containerMDGetSetName) { auto rootContainer = view()->createContainer("/root/", true); ASSERT_EQ("root", rootContainer->getName()); eos::IContainerMD::IContainerMDWriteLocker rootLocker(rootContainer); rootContainer->setName("newname"); ASSERT_EQ("newname", rootContainer->getName()); } TEST_F(HierarchicalViewF, containerMDBasicGettersSetters) { auto rootContainer = view()->createContainer("/root/", true); eos::IContainerMD::IContainerMDWriteLocker rootLocker(rootContainer); rootContainer->setCUid(2); ASSERT_EQ(2, rootContainer->getCUid()); rootContainer->setCGid(23); ASSERT_EQ(23, rootContainer->getCGid()); rootContainer->setCloneId(42); ASSERT_EQ(42, rootContainer->getCloneId()); rootContainer->setCloneFST("clone_fst"); ASSERT_EQ("clone_fst", rootContainer->getCloneFST()); rootContainer->setMode(S_IRWXU); ASSERT_EQ(S_IRWXU, rootContainer->getMode()); rootContainer->setTreeSize(64); ASSERT_EQ(64, rootContainer->getTreeSize()); eos::IContainerMD::ctime_t tnow; clock_gettime(CLOCK_REALTIME, &tnow); rootContainer->setCTime(tnow); eos::IContainerMD::ctime_t containerTime; rootContainer->getCTime(containerTime); ASSERT_EQ(tnow.tv_sec, containerTime.tv_sec); rootContainer->setMTime(tnow); rootContainer->getMTime(containerTime); ASSERT_EQ(tnow.tv_sec, containerTime.tv_sec); eos::IContainerMD::tmtime_t containerTMTime; rootContainer->setTMTimeNow(); rootContainer->getTMTime(containerTMTime); rootContainer->setTMTimeNow(); eos::IContainerMD::tmtime_t newContainerTMTime; rootContainer->getTMTime(newContainerTMTime); ASSERT_NE(containerTMTime.tv_nsec, newContainerTMTime.tv_nsec); } TEST_F(HierarchicalViewF, containerMDSyncTimeAccounting) { auto containerSyncTimeAccounting = view()->createContainer("/root/test/containersynctimeaccounting/", true); eos::IContainerMDPtr rootContainer; eos::IContainerMD::ctime_t rootContainerTimeBeforeNotify, rootContainerMTimeAfterNotify; { eos::IContainerMD::IContainerMDWriteLocker containerSyncTimeAccountingLocker(containerSyncTimeAccounting); containerSyncTimeAccounting->setAttribute("sys.mtime.propagation", "true"); auto testContainer = view()->getContainer("/root/test/"); testContainer->setAttribute("sys.mtime.propagation", "true"); rootContainer = view()->getContainer("/root/"); rootContainer->setAttribute("sys.mtime.propagation", "true"); rootContainer->setMTimeNow(); rootContainer->getTMTime(rootContainerTimeBeforeNotify); ::sleep(1); containerSyncTimeAccounting->setMTimeNow(); containerSyncTimeAccounting->notifyMTimeChange(containerSvc()); view()->updateContainerStore(containerSyncTimeAccounting.get()); } //Sleep 6 seconds the time the Container Accounting Thread does its job... ::sleep(6); rootContainer->getTMTime(rootContainerMTimeAfterNotify); ASSERT_EQ(rootContainerTimeBeforeNotify.tv_sec + 1, rootContainerMTimeAfterNotify.tv_sec); } TEST_F(HierarchicalViewF, containerMDAttributesOps) { auto rootContainer = view()->createContainer("/root/", true); eos::IContainerMD::IContainerMDWriteLocker rootLocker(rootContainer); rootContainer->setAttribute("attribute1", "value1"); rootContainer->setAttribute("attribute2", "value2"); ASSERT_TRUE(rootContainer->hasAttribute("attribute1")); ASSERT_EQ("value1", rootContainer->getAttribute("attribute1")); ASSERT_THROW(rootContainer->getAttribute("DOES_NOT_EXIST"), eos::MDException); ASSERT_EQ(2, rootContainer->numAttributes()); ASSERT_EQ(2, rootContainer->getAttributes().size()); rootContainer->removeAttribute("attribute1"); ASSERT_EQ(1, rootContainer->numAttributes()); } TEST_F(HierarchicalViewF, getFileOrContainerLockedShouldThrow) { ASSERT_THROW(eos::IContainerMD::IContainerMDWriteLocker(nullptr),eos::MDException); ASSERT_THROW(eos::IFileMD::IFileMDWriteLocker(nullptr),eos::MDException); } TEST_F(HierarchicalViewF, getFileOrContainerMDLocked) { view()->createContainer("/root/", true); auto file = view()->createFile("/root/file1"); { auto containerWriteLocked = view()->getContainerWriteLocked("/root/"); containerWriteLocked->getUnderlyingPtr()->setAttribute("testKey", "testValue"); auto file1WriteLocked = view()->getFileWriteLocked("/root/file1"); containerWriteLocked->getUnderlyingPtr()->addFile( file1WriteLocked->getUnderlyingPtr().get()); } { auto containerReadLock = view()->getContainerReadLocked("/root/"); ASSERT_EQ("testValue", containerReadLock->getUnderlyingPtr()->getAttribute("testKey")); ASSERT_EQ(file->getContainerId(), containerReadLock->getUnderlyingPtr()->getId()); } } TEST_F(HierarchicalViewF, getFileWhileBeingWriteLocked) { using namespace std::chrono_literals; view()->createContainer("/root/", true); auto file = view()->createFile("/root/file1"); //Create two threads, one will write lock the file and wait X seconds, one will //try to retrieve the file from the view std::atomic fileLocked = false; uint8_t sleepSeconds = 3; auto threadWriteLockingFile = std::thread([&file, &fileLocked, sleepSeconds]() { eos::IFileMD::IFileMDWriteLocker fileLocker(file); fileLocked = true; std::this_thread::sleep_for(std::chrono::duration(sleepSeconds + 0.1)); }); std::chrono::time_point start; std::chrono::time_point stop; auto threadGetFile = std::thread([this, &start, &stop, &fileLocked]() { while (!fileLocked) { std::this_thread::sleep_for(std::chrono::milliseconds(100ms)); } start = std::chrono::steady_clock::now(); view()->getFile("/root/file1"); stop = std::chrono::steady_clock::now(); }); threadWriteLockingFile.join(); threadGetFile.join(); auto diff = std::chrono::duration_cast (stop - start).count(); ASSERT_EQ(sleepSeconds, diff); } TEST_F(HierarchicalViewF, getFileAfterBeingRenamed) { auto root = view()->createContainer("/root/", true); auto file = view()->createFile("/root/file1"); //Create two threads, one will write lock the file and rename it, one will //wait X seconds, asks the view to retrieve it and see if it locks. std::atomic fileRenamed = false; std::atomic threadRenameStarted = false; uint8_t sleepSeconds = 3; auto threadWriteLockingFile = std::thread([this, &file, &root, &fileRenamed, &threadRenameStarted, sleepSeconds]() { threadRenameStarted = true; eos::IFileMD::IFileMDWriteLocker fileLocker(file); view()->renameFile(file.get(), "file2"); view()->updateContainerStore(root.get()); fileRenamed = true; std::this_thread::sleep_for(std::chrono::seconds(sleepSeconds)); }); std::chrono::time_point start; std::chrono::time_point stop; auto threadGetFile = std::thread([this, &start, &stop, &fileRenamed, &threadRenameStarted]() { while (!threadRenameStarted) {} while (!fileRenamed) {} ASSERT_THROW(view()->getFileReadLocked("/root/file1"), eos::MDException); start = std::chrono::steady_clock::now(); auto file2 = view()->getFileReadLocked("/root/file2"); stop = std::chrono::steady_clock::now(); ASSERT_EQ("file2", file2->getUnderlyingPtr()->getName()); }); threadWriteLockingFile.join(); threadGetFile.join(); auto diff = std::chrono::duration_cast (stop - start).count(); ASSERT_EQ(sleepSeconds, diff); } TEST_F(HierarchicalViewF, getFileOrContainerWriteLockedTwiceInSameThread) { { view()->createContainer("/root/", true); view()->createFile("/root/file1"); } auto cont = view()->getContainerWriteLocked("/root/"); auto cont2 = view()->getContainerWriteLocked("/root/"); auto cont3 = view()->getContainerReadLocked("/root/"); auto file = view()->getFileWriteLocked("/root/file1"); auto file2 = view()->getFileWriteLocked("/root/file1"); auto file3 = view()->getFileReadLocked("/root/file1"); ASSERT_EQ(cont->getUnderlyingPtr().get(), cont2->getUnderlyingPtr().get()); ASSERT_EQ(cont->getUnderlyingPtr().get(), cont3->getUnderlyingPtr().get()); ASSERT_EQ(file->getUnderlyingPtr().get(), file2->getUnderlyingPtr().get()); ASSERT_EQ(file->getUnderlyingPtr().get(), file3->getUnderlyingPtr().get()); } TEST_F(HierarchicalViewF, NSObjectLockerNoDeadlockIfLockDestroyedAfterOwnedSharedPtr) { eos::IContainerMD::IContainerMDWriteLockerPtr contLock; { auto cont = view()->createContainer("/root/", true); contLock = view()->getContainerWriteLocked("/root/"); containerSvc()->removeContainer(cont.get()); cont = nullptr; ASSERT_THROW( view()->getContainer("/root/"),eos::MDException); } //If you have a deadlock here in the destructor of the contLock, then something is wrong... } TEST_F(HierarchicalViewF, ContainerFindContainerOrFileReadWriteLocked){ auto cont = view()->createContainer("/root/", true); ASSERT_EQ(nullptr, cont->findContainerReadLocked("does_not_exist")); auto testCont = view()->createContainer("/root/test/"); auto contWriteLocked = cont->findContainerWriteLocked("test"); ASSERT_NE(nullptr, contWriteLocked); ASSERT_EQ(testCont, contWriteLocked->getUnderlyingPtr()); // as the container already has a write lock, read lock it will not deadlock auto contReadLocked = cont->findContainerReadLocked("test"); ASSERT_NE(nullptr, contReadLocked); ASSERT_EQ(testCont,contReadLocked->getUnderlyingPtr()); auto file = view()->createFile("/root/test/file1"); auto cont2 = contWriteLocked->getUnderlyingPtr(); ASSERT_EQ(nullptr,cont2->findFileReadLocked("does_not_exist")); auto fileReadLocked = cont2->findFileReadLocked("file1"); ASSERT_NE(nullptr, fileReadLocked); ASSERT_EQ(file, fileReadLocked->getUnderlyingPtr()); fileReadLocked.reset(); auto fileWriteLocked = cont2->findFileWriteLocked("file1"); ASSERT_NE(nullptr,fileWriteLocked); ASSERT_EQ(file,fileWriteLocked->getUnderlyingPtr()); } TEST_F(HierarchicalViewF, ContainerFindItemReadWriteLocked) { auto cont = view()->createContainer("/root/", true); auto cont2 = view()->createContainer("/root/test/"); auto file = view()->createFile("/root/test/file1"); auto fileOrContainerLockedNotExist = cont->findItemReadLocked("does_not_exist"); ASSERT_EQ(nullptr,fileOrContainerLockedNotExist.fileLocked); ASSERT_EQ(nullptr,fileOrContainerLockedNotExist.containerLocked); auto testContWriteLocked = cont->findItemWriteLocked("test"); ASSERT_EQ(nullptr,testContWriteLocked.fileLocked); ASSERT_EQ(cont2,testContWriteLocked.containerLocked->getUnderlyingPtr()); auto file1Locked = cont2->findItemReadLocked("file1"); ASSERT_EQ(nullptr,file1Locked.containerLocked); ASSERT_EQ(file,file1Locked.fileLocked->getUnderlyingPtr()); }