mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
107
tests/meta/store/TestAuth.cc
Normal file
107
tests/meta/store/TestAuth.cc
Normal file
@@ -0,0 +1,107 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/futures/Barrier.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "core/user/UserStore.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/storage/Common.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestAuth : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestAuth, KVTypes);
|
||||
|
||||
TYPED_TEST(TestAuth, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().set_authenticate(true);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
flat::Uid rootId(0), aliceId(1), bobId(2), charlieId(3);
|
||||
UserAttr root, alic, bob, charlie;
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
core::UserStore userStore;
|
||||
root = (co_await userStore.addUser(*txn, rootId, "root", {}, true)).value();
|
||||
alic = (co_await userStore.addUser(*txn, aliceId, "alic", {flat::Gid(2), flat::Gid(1001)}, true)).value();
|
||||
bob = (co_await userStore.addUser(*txn, bobId, "bob", {flat::Gid(1001)}, false)).value();
|
||||
charlie = (co_await userStore.addUser(*txn, charlieId, "charlie", {}, false)).value();
|
||||
});
|
||||
|
||||
std::vector<std::pair<UserInfo, UserInfo>> succ{
|
||||
// root & sudoer can auth with any uid that exists
|
||||
{{rootId, root.gid, root.token}, {rootId, root.gid, root.groups, root.token}},
|
||||
{{aliceId, alic.gid, root.token}, {aliceId, alic.gid, alic.groups, root.token}},
|
||||
{{bobId, bob.gid, root.token}, {bobId, bob.gid, bob.groups, root.token}},
|
||||
{{bobId, charlie.gid, root.token}, {bobId, charlie.gid, bob.groups, root.token}},
|
||||
{{rootId, root.gid, alic.token}, {rootId, root.gid, root.groups, alic.token}},
|
||||
{{aliceId, alic.gid, alic.token}, {aliceId, alic.gid, alic.groups, alic.token}},
|
||||
{{bobId, bob.gid, alic.token}, {bobId, bob.gid, bob.groups, alic.token}},
|
||||
{{bobId, charlie.gid, alic.token}, {bobId, charlie.gid, bob.groups, alic.token}},
|
||||
// other user can only auth with self uid and gid
|
||||
{{bobId, bob.gid, bob.token}, {bobId, bob.gid, bob.groups, bob.token}},
|
||||
{{charlieId, charlie.gid, charlie.token}, {charlieId, charlie.gid, charlie.groups, charlie.token}},
|
||||
};
|
||||
for (auto [req, expected] : succ) {
|
||||
auto result = co_await meta.authenticate(AuthReq(req));
|
||||
CO_ASSERT_OK(result);
|
||||
const auto &actual = result->user;
|
||||
CO_ASSERT_EQ(actual, expected) << fmt::format("{} vs {}; ", actual, expected)
|
||||
<< fmt::format("{} vs {}",
|
||||
fmt::join(actual.groups.begin(), actual.groups.end(), ","),
|
||||
fmt::join(expected.groups.begin(), expected.groups.end(), ","));
|
||||
}
|
||||
|
||||
std::vector<UserInfo> fail{// don't allow unknown uid
|
||||
{flat::Uid(999), flat::Gid(0), root.token},
|
||||
{flat::Uid(1024), flat::Gid(0), root.token},
|
||||
// other user can only auth with self uid and gid
|
||||
{bobId, charlie.gid, bob.token},
|
||||
{charlieId, bob.gid, bob.token},
|
||||
{charlieId, bob.gid, charlie.token},
|
||||
{bobId, charlie.gid, charlie.token},
|
||||
// invalid token
|
||||
{bobId, bob.gid, ""}};
|
||||
for (const auto &req : fail) {
|
||||
fmt::print("{}\n", req);
|
||||
auto result = co_await meta.authenticate(AuthReq(req));
|
||||
CO_ASSERT_ERROR(result, StatusCode::kAuthenticationFail);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
139
tests/meta/store/TestDirEntry.cc
Normal file
139
tests/meta/store/TestDirEntry.cc
Normal file
@@ -0,0 +1,139 @@
|
||||
#include <cstdlib>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/IKVEngine.h"
|
||||
#include "common/kv/mem/MemKVEngine.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/String.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
using namespace hf3fs::kv;
|
||||
|
||||
class TestDirEntry : public ::testing::Test {
|
||||
protected:
|
||||
MemKVEngine engine_;
|
||||
};
|
||||
|
||||
const Acl acl(Uid(0), Gid(0), Permission(0777));
|
||||
|
||||
TEST_F(TestDirEntry, LoadStore) {
|
||||
srand(time(nullptr));
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
static constexpr size_t kDirEntries = 1000;
|
||||
std::vector<DirEntry> entries;
|
||||
for (size_t i = 0; i < kDirEntries; i++) {
|
||||
auto entry = MetaTestHelper::randomDirEntry();
|
||||
auto storeTxn = engine_.createReadWriteTransaction();
|
||||
auto storeResult = co_await entry.store(*storeTxn);
|
||||
CO_ASSERT_TRUE(storeResult.hasValue());
|
||||
auto commitResult = co_await storeTxn->commit();
|
||||
CO_ASSERT_OK(commitResult);
|
||||
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
auto loadTxn = engine_.createReadonlyTransaction();
|
||||
for (auto &entry : entries) {
|
||||
auto loadResult =
|
||||
(co_await DirEntry::snapshotLoad(*loadTxn, entry.parent, entry.name)).then(checkMetaFound<DirEntry>);
|
||||
CO_ASSERT_OK(loadResult);
|
||||
CO_ASSERT_EQ(entry, *loadResult);
|
||||
}
|
||||
|
||||
for (auto &entry : entries) {
|
||||
auto removeTxn = engine_.createReadWriteTransaction();
|
||||
auto removeResult = co_await entry.remove(*removeTxn);
|
||||
CO_ASSERT_TRUE(removeResult.hasValue());
|
||||
auto commitResult = co_await removeTxn->commit();
|
||||
CO_ASSERT_TRUE(commitResult.hasValue());
|
||||
}
|
||||
|
||||
for (auto &entry : entries) {
|
||||
DirEntry other(entry.parent, entry.name);
|
||||
auto loadTxn = engine_.createReadonlyTransaction();
|
||||
auto loadResult =
|
||||
(co_await DirEntry::snapshotLoad(*loadTxn, entry.parent, entry.name)).then(checkMetaFound<DirEntry>);
|
||||
CO_ASSERT_ERROR(loadResult, MetaCode::kNotFound);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestDirEntry, List) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
std::vector<DirEntry> entries;
|
||||
InodeId parent = MetaTestHelper::randomInodeId();
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
String name = fmt::format("file-{}", i);
|
||||
InodeId id = MetaTestHelper::randomInodeId();
|
||||
DirEntry entry = DirEntry::newFile(parent, name, id);
|
||||
|
||||
auto storeTxn = engine_.createReadWriteTransaction();
|
||||
auto storeResult = co_await entry.store(*storeTxn);
|
||||
CO_ASSERT_TRUE(storeResult.hasValue());
|
||||
|
||||
auto commitResult = co_await storeTxn->commit();
|
||||
CO_ASSERT_OK(commitResult);
|
||||
}
|
||||
|
||||
auto loadTxn = engine_.createReadonlyTransaction();
|
||||
auto listResult = co_await DirEntryList::snapshotLoad(*loadTxn, parent, {}, 10, false);
|
||||
CO_ASSERT_TRUE(listResult.hasValue());
|
||||
auto list = listResult.value();
|
||||
CO_ASSERT_EQ(list.entries.size(), 10);
|
||||
CO_ASSERT_TRUE(list.inodes.empty());
|
||||
CO_ASSERT_TRUE(list.more);
|
||||
|
||||
// load last entries
|
||||
listResult = co_await DirEntryList::snapshotLoad(*loadTxn, parent, list.entries.at(9).name, 90, false);
|
||||
CO_ASSERT_TRUE(listResult.hasValue());
|
||||
list = listResult.value();
|
||||
CO_ASSERT_EQ(list.entries.size(), 90);
|
||||
CO_ASSERT_TRUE(list.inodes.empty());
|
||||
CO_ASSERT_FALSE(list.more);
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestDirEntry, Conflict) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// txn1 update entry, txn2 include entry into it's read conflict set (by load or addReadConflict) and set a random
|
||||
// key, txn1 commit first, txn2 should fail
|
||||
DirEntry entry(MetaTestHelper::randomInodeId(), std::to_string(i));
|
||||
entry.id = MetaTestHelper::randomInodeId();
|
||||
auto txn1 = engine_.createReadWriteTransaction();
|
||||
auto txn2 = engine_.createReadWriteTransaction();
|
||||
|
||||
auto store = co_await entry.store(*txn1);
|
||||
CO_ASSERT_OK(store);
|
||||
|
||||
if (rand() % 2) {
|
||||
CO_ASSERT_OK(co_await DirEntry::load(*txn2, entry.parent, entry.name));
|
||||
} else {
|
||||
auto addConflict = co_await entry.addIntoReadConflict(*txn2);
|
||||
CO_ASSERT_OK(addConflict);
|
||||
}
|
||||
auto set = co_await txn2->set(std::to_string(rand()), std::to_string(rand()));
|
||||
CO_ASSERT_OK(set);
|
||||
|
||||
auto txn1Result = co_await txn1->commit();
|
||||
CO_ASSERT_OK(txn1Result);
|
||||
auto txn2Result = co_await txn2->commit();
|
||||
CO_ASSERT_TRUE(txn2Result.hasError());
|
||||
CO_ASSERT_EQ(txn2Result.error().code(), TransactionCode::kConflict);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
244
tests/meta/store/TestInode.cc
Normal file
244
tests/meta/store/TestInode.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
#include <cstdlib>
|
||||
#include <double-conversion/utils.h>
|
||||
#include <fmt/compile.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/IKVEngine.h"
|
||||
#include "common/kv/mem/MemKVEngine.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/mgmtd/ChainRef.h"
|
||||
#include "fbs/mgmtd/MgmtdTypes.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/Utils.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
using flat::ChainId;
|
||||
using flat::ChainRef;
|
||||
using flat::ChainTableId;
|
||||
using flat::ChainTableVersion;
|
||||
|
||||
TEST(TestInodeId, Special) {
|
||||
auto root = InodeId::root();
|
||||
auto gcRoot = InodeId::gcRoot();
|
||||
ASSERT_NE(root, gcRoot);
|
||||
ASSERT_EQ(root.u64(), 0);
|
||||
ASSERT_EQ(gcRoot.u64(), 1);
|
||||
ASSERT_EQ(root, InodeId::root());
|
||||
ASSERT_EQ(gcRoot, InodeId::gcRoot());
|
||||
|
||||
ASSERT_EQ(fmt::format("{}", root), "0x0000000000000000");
|
||||
ASSERT_EQ(fmt::format("{}", gcRoot), "0x0000000000000001");
|
||||
ASSERT_EQ(root.toHexString(), "0x0000000000000000");
|
||||
ASSERT_EQ(gcRoot.toHexString(), "0x0000000000000001");
|
||||
}
|
||||
|
||||
template <typename KV>
|
||||
class TestInode : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestInode, KVTypes);
|
||||
|
||||
TYPED_TEST(TestInode, File) {
|
||||
Inode inode = Inode::newFile(MetaTestHelper::randomInodeId(),
|
||||
Acl(Uid(0), Gid(0), Permission(0644)),
|
||||
Layout(),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
|
||||
ASSERT_TRUE(inode.isFile());
|
||||
ASSERT_FALSE(inode.isDirectory());
|
||||
ASSERT_FALSE(inode.isSymlink());
|
||||
ASSERT_EQ(inode.getType(), InodeType::File);
|
||||
ASSERT_NO_THROW(inode.asFile());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, FileLayout) {
|
||||
auto layout = Layout::newEmpty(ChainTableId(1), 1000, 4);
|
||||
ASSERT_TRUE(std::holds_alternative<Layout::Empty>(layout.chains));
|
||||
ASSERT_FALSE(layout.valid(true));
|
||||
layout = Layout::newEmpty(ChainTableId(1), 1024, 4);
|
||||
ASSERT_TRUE(layout.valid(true));
|
||||
ASSERT_FALSE(layout.valid(false));
|
||||
ASSERT_EQ(layout.getChainIndexList().size(), 0);
|
||||
|
||||
layout.chains = Layout::ChainList{{1}};
|
||||
ASSERT_FALSE(layout.valid(false));
|
||||
|
||||
layout.chains = Layout::ChainList{{1, 2, 3, 4}};
|
||||
ASSERT_FALSE(layout.valid(false)) << fmt::format("{}", layout);
|
||||
layout.tableVersion = ChainTableVersion(1);
|
||||
ASSERT_TRUE(layout.valid(false)) << fmt::format("{}", layout);
|
||||
ASSERT_TRUE(std::holds_alternative<Layout::ChainList>(layout.chains));
|
||||
ASSERT_EQ(layout.getChainIndexList().size(), layout.stripeSize);
|
||||
|
||||
layout.chains = Layout::Empty();
|
||||
layout.chains = Layout::ChainRange(1, Layout::ChainRange::STD_SHUFFLE_MT19937, 0);
|
||||
ASSERT_TRUE(layout.valid(false));
|
||||
ASSERT_TRUE(std::holds_alternative<Layout::ChainRange>(layout.chains));
|
||||
ASSERT_EQ(layout.getChainIndexList().size(), layout.stripeSize);
|
||||
|
||||
std::set<uint32_t> set;
|
||||
for (auto chain : layout.getChainIndexList()) {
|
||||
ASSERT_FALSE(set.contains(chain));
|
||||
set.emplace(chain);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, ChunkId) {
|
||||
Inode inode = Inode::newFile(MetaTestHelper::randomInodeId(),
|
||||
Acl(Uid(0), Gid(0), Permission(0644)),
|
||||
Layout(),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
ASSERT_FALSE(inode.asFile().getChunkId(inode.id, 0));
|
||||
|
||||
inode.asFile().layout = Layout::newEmpty(ChainTableId(1), 4096, 1);
|
||||
ASSERT_TRUE(inode.asFile().getChunkId(inode.id, 0));
|
||||
ASSERT_EQ(*inode.asFile().getChunkId(inode.id, 100), ChunkId(inode.id, 0, 0));
|
||||
ASSERT_EQ(*inode.asFile().getChunkId(inode.id, 9999), ChunkId(inode.id, 0, 2));
|
||||
ASSERT_EQ(inode.asFile().getChunkId(inode.id, 1ul << 63).error().code(), MetaCode::kFileTooLarge);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto inode = meta::InodeId(folly::Random::rand64());
|
||||
auto chunk = folly::Random::rand32();
|
||||
ChunkId chunkId(inode, 0, chunk);
|
||||
ASSERT_EQ(chunkId.chunk(), chunk);
|
||||
ASSERT_EQ(chunkId.inode(), inode);
|
||||
auto packed = chunkId.pack();
|
||||
ASSERT_EQ(chunkId, ChunkId::unpack(packed));
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, SerializedSize) {
|
||||
auto layout = Layout::newEmpty(ChainTableId(1), 4096, 1);
|
||||
layout.chains = Layout::ChainRange(5, Layout::ChainRange::NO_SHUFFLE, 0);
|
||||
auto file = Inode::newFile(MetaTestHelper::randomInodeId(),
|
||||
Acl(Uid(0), Gid(0), Permission(0644)),
|
||||
layout,
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
file.asFile().length = 100_MB;
|
||||
fmt::print("File with range layout, serialized {} bytes, data {} type {}, asFile {}.\n",
|
||||
serde::serialize(file).size(),
|
||||
serde::serialize(file.data()).size(),
|
||||
serde::serialize(file.data().type).size(),
|
||||
serde::serialize(file.data().asFile()).size());
|
||||
|
||||
auto directory = Inode::newDirectory(MetaTestHelper::randomInodeId(),
|
||||
MetaTestHelper::randomInodeId(),
|
||||
"directory-name",
|
||||
Acl(Uid(0), Gid(0), Permission(0644)),
|
||||
layout,
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
fmt::print("Directory with layout, serialized {} {} bytes.\n",
|
||||
serde::serialize(directory.data()).size(),
|
||||
serde::serialize(directory).size());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, Directory) {
|
||||
Inode inode = Inode::newDirectory(MetaTestHelper::randomInodeId(),
|
||||
MetaTestHelper::randomInodeId(),
|
||||
"directory",
|
||||
Acl(Uid(0), Gid(0), Permission(0644)),
|
||||
Layout::newEmpty(ChainTableId(1), 512 << 10, 64),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
|
||||
ASSERT_FALSE(inode.isFile());
|
||||
ASSERT_TRUE(inode.isDirectory());
|
||||
ASSERT_FALSE(inode.isSymlink());
|
||||
ASSERT_EQ(inode.getType(), InodeType::Directory);
|
||||
ASSERT_NO_THROW(inode.asDirectory());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, Symlink) {
|
||||
Inode inode = Inode::newSymlink(MetaTestHelper::randomInodeId(),
|
||||
std::to_string(folly::Random::rand32()),
|
||||
Uid(0),
|
||||
Gid(0),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
|
||||
ASSERT_FALSE(inode.isFile());
|
||||
ASSERT_FALSE(inode.isDirectory());
|
||||
ASSERT_TRUE(inode.isSymlink());
|
||||
ASSERT_EQ(inode.getType(), InodeType::Symlink);
|
||||
ASSERT_NO_THROW(inode.asSymlink());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, LoadStore) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
static constexpr size_t kInodes = 1000;
|
||||
std::vector<Inode> inodes;
|
||||
for (size_t i = 0; i < kInodes; i++) {
|
||||
auto inode = MetaTestHelper::randomInode();
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto storeResult = co_await inode.store(*txn);
|
||||
CO_ASSERT_OK(storeResult);
|
||||
});
|
||||
|
||||
inodes.push_back(inode);
|
||||
}
|
||||
|
||||
for (auto &inode : inodes) {
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto loadResult = (co_await Inode::snapshotLoad(*txn, inode.id)).then(checkMetaFound<Inode>);
|
||||
CO_ASSERT_OK(loadResult);
|
||||
auto other = std::move(loadResult.value());
|
||||
CO_ASSERT_EQ(inode, other) << inode.isFile();
|
||||
if (inode.isFile()) {
|
||||
auto cnts = inode.asFile().layout.getChainIndexList().size();
|
||||
CO_ASSERT_EQ(cnts, other.asFile().layout.getChainIndexList().size());
|
||||
for (size_t i = 0; i < cnts; i++) {
|
||||
CO_ASSERT_EQ(inode.asFile().layout.getChainIndexList()[i], other.asFile().layout.getChainIndexList()[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto &inode : inodes) {
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto removeResult = co_await inode.remove(*txn);
|
||||
CO_ASSERT_OK(removeResult);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto &inode : inodes) {
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto loadResult = (co_await Inode::snapshotLoad(*txn, inode.id)).then(checkMetaFound<Inode>);
|
||||
CO_ASSERT_ERROR(loadResult, MetaCode::kNotFound);
|
||||
});
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInode, Conflict) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// txn1 update inode, txn2 include inode into it's read conflict set (by load or addReadConflict) and set a random
|
||||
// key, txn1 commit first, txn2 should fail
|
||||
Inode inode = MetaTestHelper::randomInode();
|
||||
CO_ASSERT_CONFLICT([&](auto &txn) -> CoTask<void> { CO_ASSERT_OK(co_await inode.store(txn)); },
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
if (folly::Random::rand32() % 2) {
|
||||
CO_ASSERT_OK(co_await Inode::load(txn, inode.id));
|
||||
} else {
|
||||
// use snapshotLoad to get a read version
|
||||
CO_ASSERT_OK(co_await Inode::snapshotLoad(txn, inode.id));
|
||||
CO_ASSERT_OK(co_await Inode(inode.id).addIntoReadConflict(txn));
|
||||
}
|
||||
auto set = co_await txn.set(std::to_string(folly::Random::rand32()),
|
||||
std::to_string(folly::Random::rand32()));
|
||||
CO_ASSERT_OK(set);
|
||||
});
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
184
tests/meta/store/ops/TestBatch.cc
Normal file
184
tests/meta/store/ops/TestBatch.cc
Normal file
@@ -0,0 +1,184 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/futures/Barrier.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "fbs/storage/Common.h"
|
||||
#include "fdb/FDBRetryStrategy.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/ops/BatchOperation.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestBatchOp : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestBatchOp, KVTypes);
|
||||
|
||||
TYPED_TEST(TestBatchOp, basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
auto &engine = *this->kvEngine();
|
||||
|
||||
// create a inodeId to test
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
auto inodeId = create->stat.id;
|
||||
|
||||
auto u1 = flat::UserInfo(flat::Uid(1), flat::Gid(1), "");
|
||||
auto u2 = flat::UserInfo(flat::Uid(2), flat::Gid(2), "");
|
||||
|
||||
auto waiter1 = BatchedOp::Waiter<SetAttrReq, SetAttrRsp>(
|
||||
SetAttrReq::setPermission(SUPER_USER, PathAt(inodeId), AtFlags(), flat::Uid(1), std::nullopt, std::nullopt));
|
||||
auto waiter2 = BatchedOp::Waiter<SetAttrReq, SetAttrRsp>(
|
||||
SetAttrReq::setPermission(u1, PathAt(inodeId), AtFlags(), std::nullopt, flat::Gid(1), std::nullopt));
|
||||
auto waiter3 = BatchedOp::Waiter<SetAttrReq, SetAttrRsp>(
|
||||
SetAttrReq::setPermission(u2, PathAt(inodeId), AtFlags(), std::nullopt, flat::Gid(2), std::nullopt));
|
||||
auto waiter4 = BatchedOp::Waiter<SyncReq, SyncRsp>(
|
||||
SyncReq(SUPER_USER, inodeId, true, SETATTR_TIME_NOW, SETATTR_TIME_NOW, false, meta::VersionedLength{1000, 0}));
|
||||
auto waiter5 = BatchedOp::Waiter<CloseReq, CloseRsp>(
|
||||
CloseReq(SUPER_USER, inodeId, MetaTestHelper::randomSession(), true, std::nullopt, std::nullopt));
|
||||
auto waiter6 = BatchedOp::Waiter<CloseReq, CloseRsp>(
|
||||
CloseReq(SUPER_USER, inodeId, std::nullopt, true, std::nullopt, std::nullopt));
|
||||
|
||||
{
|
||||
FAULT_INJECTION_SET(10, 3);
|
||||
auto batch = std::make_unique<BatchedOp>(store, inodeId);
|
||||
batch->add(waiter1);
|
||||
batch->add(waiter2);
|
||||
batch->add(waiter3);
|
||||
batch->add(waiter4);
|
||||
batch->retry(Status(StatusCode::kInvalidArg));
|
||||
|
||||
auto txn = engine.createReadWriteTransaction();
|
||||
auto driver = OperationDriver(*batch, Void{});
|
||||
auto result = co_await driver.run(std::move(txn), {}, false, false);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->asFile().getVersionedLength(), (meta::VersionedLength{1000, 0}));
|
||||
CO_ASSERT_EQ(result->acl.uid, flat::Uid(1));
|
||||
CO_ASSERT_EQ(result->acl.gid, flat::Gid(1));
|
||||
|
||||
CO_ASSERT_OK(waiter1.getResult());
|
||||
CO_ASSERT_OK(waiter2.getResult());
|
||||
CO_ASSERT_ERROR(waiter3.getResult(), MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(waiter4.getResult());
|
||||
};
|
||||
|
||||
{
|
||||
FAULT_INJECTION_SET(10, 3);
|
||||
auto batch = std::make_unique<BatchedOp>(store, inodeId);
|
||||
batch->add(waiter4);
|
||||
batch->add(waiter5);
|
||||
batch->add(waiter6);
|
||||
batch->retry(Status(StatusCode::kInvalidArg));
|
||||
|
||||
auto txn = engine.createReadWriteTransaction();
|
||||
auto driver = OperationDriver(*batch, Void{});
|
||||
auto result = co_await driver.run(std::move(txn), {}, false, false);
|
||||
if (!result.hasError()) {
|
||||
CO_ASSERT_EQ(result->asFile().getVersionedLength(), (meta::VersionedLength{0, 1}));
|
||||
CO_ASSERT_OK(waiter4.getResult());
|
||||
CO_ASSERT_OK(waiter5.getResult());
|
||||
CO_ASSERT_ERROR(waiter6.getResult(), StatusCode::kInvalidArg);
|
||||
}
|
||||
};
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBatchOp, batch) {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto req1 = SyncReq(SUPER_USER, InodeId(1), true, {}, {});
|
||||
auto req2 = SyncReq(SUPER_USER, InodeId(2), true, {}, {});
|
||||
|
||||
BatchedOp::Waiter<SyncReq, SyncRsp> waiter1(req1), waiter2(req1), waiter3(req1), waiter4(req2), waiter5(req1);
|
||||
auto batch1 = meta.addBatchReq(InodeId(1), waiter1);
|
||||
CO_ASSERT_NE(batch1.get(), nullptr);
|
||||
auto batch2 = meta.addBatchReq(InodeId(1), waiter2);
|
||||
CO_ASSERT_NE(batch2.get(), nullptr);
|
||||
auto batch3 = meta.addBatchReq(InodeId(1), waiter3);
|
||||
CO_ASSERT_EQ(batch3.get(), nullptr);
|
||||
auto batch4 = meta.addBatchReq(InodeId(2), waiter4);
|
||||
CO_ASSERT_NE(batch4.get(), nullptr);
|
||||
CO_ASSERT_NE(batch1.get(), batch4.get());
|
||||
|
||||
// batch 1 can run, but batch 2 can't
|
||||
CO_ASSERT_TRUE(waiter1.baton.ready());
|
||||
CO_ASSERT_FALSE(waiter2.baton.ready());
|
||||
CO_ASSERT_OK(co_await meta.runBatch(InodeId(1), std::move(batch1)));
|
||||
CO_ASSERT_TRUE(waiter2.baton.ready());
|
||||
CO_ASSERT_FALSE(waiter3.baton.ready());
|
||||
CO_ASSERT_OK(co_await meta.runBatch(InodeId(1), std::move(batch2)));
|
||||
CO_ASSERT_TRUE(waiter3.baton.ready());
|
||||
|
||||
auto batch5 = meta.addBatchReq(InodeId(1), waiter5);
|
||||
CO_ASSERT_NE(batch5.get(), nullptr);
|
||||
CO_ASSERT_TRUE(waiter5.baton.ready());
|
||||
|
||||
std::unique_ptr<BatchedOp> batch6;
|
||||
std::vector<std::unique_ptr<BatchedOp::Waiter<SyncReq, SyncRsp>>> waiters;
|
||||
for (size_t i = 0; i < 5000; i++) {
|
||||
auto waiter = std::make_unique<BatchedOp::Waiter<SyncReq, SyncRsp>>(req1);
|
||||
auto batch = meta.addBatchReq(InodeId(1), *waiter);
|
||||
if (!batch6) {
|
||||
CO_ASSERT_TRUE(batch);
|
||||
batch6 = std::move(batch);
|
||||
}
|
||||
if (i >= cluster.config().mock_meta().max_batch_operations()) {
|
||||
CO_ASSERT_TRUE(waiter->baton.ready());
|
||||
CO_ASSERT_EQ(waiter->getResult().error().code(), MetaCode::kBusy);
|
||||
} else {
|
||||
CO_ASSERT_FALSE(waiter->baton.ready()) << fmt::format("{}", i);
|
||||
}
|
||||
waiters.push_back(std::move(waiter));
|
||||
}
|
||||
|
||||
// reject timeout operation
|
||||
CO_ASSERT_ERROR(co_await meta.runBatch(InodeId(1), std::move(batch5), SteadyClock::now()),
|
||||
MetaCode::kOperationTimeout);
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
246
tests/meta/store/ops/TestClose.cc
Normal file
246
tests/meta/store/ops/TestClose.cc
Normal file
@@ -0,0 +1,246 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/futures/Barrier.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/storage/Common.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestClose : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestClose, KVTypes);
|
||||
|
||||
TYPED_TEST(TestClose, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// close directory
|
||||
CO_ASSERT_ERROR(
|
||||
co_await meta.close(
|
||||
{SUPER_USER, InodeId::root(), MetaTestHelper::randomSession(), true, std::nullopt, std::nullopt}),
|
||||
MetaCode::kNotFile);
|
||||
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
auto inode = create->stat;
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
|
||||
// file has no session
|
||||
READ_WRITE_TRANSACTION_NO_COMMIT({
|
||||
auto result = co_await FileSession::checkExists(*txn, inode.id);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_FALSE(*result);
|
||||
});
|
||||
|
||||
// open file with read
|
||||
CO_ASSERT_OK(co_await meta.open({SUPER_USER, PathAt("test-file"), {}, O_RDONLY}));
|
||||
// file has no session
|
||||
READ_WRITE_TRANSACTION_NO_COMMIT({
|
||||
auto result = co_await FileSession::checkExists(*txn, inode.id);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_FALSE(*result);
|
||||
});
|
||||
|
||||
// open file with write
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
CO_ASSERT_OK(co_await meta.open({SUPER_USER, PathAt("test-file"), session, O_WRONLY}));
|
||||
|
||||
// file has session & client has session
|
||||
READ_WRITE_TRANSACTION_NO_COMMIT({
|
||||
auto result = co_await FileSession::checkExists(*txn, inode.id);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(*result);
|
||||
auto inodeSessions = co_await FileSession::list(*txn, inode.id, false);
|
||||
CO_ASSERT_TRUE(inodeSessions.hasValue() && !inodeSessions->empty());
|
||||
// auto clientSessions = co_await FileSession::list(*txn, session.client.uuid, false);
|
||||
// CO_ASSERT_TRUE(clientSessions.hasValue() && !clientSessions->empty());
|
||||
});
|
||||
|
||||
// close file, should set session if update length is enabled
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, inode.id, {}, false, UtcClock::now(), {}}));
|
||||
CO_ASSERT_ERROR(co_await meta.close({SUPER_USER, inode.id, {}, true, UtcClock::now(), {}}),
|
||||
StatusCode::kInvalidArg);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, inode.id, session, true, {}, {}}));
|
||||
|
||||
// file has no session, client has no session
|
||||
READ_WRITE_TRANSACTION_NO_COMMIT({
|
||||
auto result = co_await FileSession::checkExists(*txn, inode.id);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_FALSE(*result);
|
||||
auto inodeSessions = co_await FileSession::list(*txn, inode.id, false);
|
||||
CO_ASSERT_TRUE(inodeSessions.hasValue() && inodeSessions->empty());
|
||||
// todo: maybe should check session count
|
||||
// or: just dump all sessions
|
||||
// auto clientSessions = co_await FileSession::list(*txn, session.client.uuid, false);
|
||||
// CO_ASSERT_TRUE(clientSessions.hasValue() && clientSessions->empty());
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
// TYPED_TEST(TestClose, CheckHole) {
|
||||
// folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// MockCluster::Config cfg;
|
||||
// cfg.mock_meta().set_check_file_hole(true);
|
||||
// auto cluster = this->createMockCluster(cfg);
|
||||
// auto &meta = cluster.meta().getOperator();
|
||||
// auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
// auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
// CO_ASSERT_OK(create);
|
||||
// Inode inode(create->stat);
|
||||
// CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
|
||||
// auto session1 = MetaTestHelper::randomSession();
|
||||
// auto session2 = MetaTestHelper::randomSession();
|
||||
// CO_ASSERT_OK(co_await meta.open({SUPER_USER, PathAt("test-file"), session1, O_WRONLY}));
|
||||
// CO_ASSERT_OK(co_await meta.open({SUPER_USER, PathAt("test-file"), session2, O_WRONLY}));
|
||||
|
||||
// // do some write to generate hole
|
||||
// co_await randomWrite(meta, storage, inode, 1 << 20, 1 << 20);
|
||||
// // close session1, return ok
|
||||
// CO_ASSERT_OK(co_await meta.close({SUPER_USER, inode.id, session1, true, {}, {}}));
|
||||
|
||||
// // close session2, return has hole
|
||||
// CO_ASSERT_ERROR(co_await meta.close({SUPER_USER, inode.id, session2, true, {}, {}}), MetaCode::kFileHasHole);
|
||||
|
||||
// // don't allow open O_RDONLY
|
||||
// CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, PathAt("test-file"), {}, O_RDONLY}), MetaCode::kFileHasHole);
|
||||
|
||||
// // open read write
|
||||
// auto session3 = MetaTestHelper::randomSession();
|
||||
// CO_ASSERT_OK(co_await meta.open({SUPER_USER, PathAt("test-file"), session3, O_WRONLY}));
|
||||
|
||||
// // write fix hole
|
||||
// co_await randomWrite(meta, storage, inode, 0, 3 << 20);
|
||||
// // close should ok
|
||||
// CO_ASSERT_OK(co_await meta.close({SUPER_USER, inode.id, session3, true, {}, {}}));
|
||||
|
||||
// // open and check length
|
||||
// auto oresult = co_await meta.open({SUPER_USER, PathAt("test-file"), {}, O_RDONLY});
|
||||
// CO_ASSERT_OK(oresult);
|
||||
// CO_ASSERT_EQ(oresult->stat.asFile().length, 3 << 20);
|
||||
// }());
|
||||
// }
|
||||
|
||||
TYPED_TEST(TestClose, Concurrent) {
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config cfg;
|
||||
cfg.mock_meta().set_check_file_hole(true);
|
||||
auto cluster = this->createMockCluster(cfg);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
auto worker = [&](Path path,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
std::atomic_int *failed,
|
||||
folly::futures::Barrier *beginBarr,
|
||||
folly::futures::Barrier *endBarr) -> CoTask<void> {
|
||||
// open, should succ
|
||||
if (!beginBarr && folly::Random::oneIn(4)) {
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(folly::Random::rand32(300)));
|
||||
}
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto oresult = co_await meta.create({SUPER_USER, path, session, O_RDWR, p644});
|
||||
CO_ASSERT_OK(oresult);
|
||||
Inode inode = oresult->stat;
|
||||
if (beginBarr) co_await beginBarr->wait();
|
||||
|
||||
co_await randomWrite(meta, storage, inode, offset, length);
|
||||
|
||||
if (endBarr) co_await endBarr->wait();
|
||||
auto cresult = co_await meta.close({SUPER_USER, inode.id, session, true, {}, UtcClock::now()});
|
||||
if (cresult.hasError()) {
|
||||
CO_ASSERT_ERROR(cresult, MetaCode::kFileHasHole);
|
||||
if (failed) (*failed)++;
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool>> tests;
|
||||
for (auto hole : {false, true}) {
|
||||
for (auto beginBarr : {false, true})
|
||||
for (auto endBarr : {false, true}) tests.push_back({beginBarr, endBarr, hole});
|
||||
}
|
||||
|
||||
for (auto [hole, beginBarr, endBarr] : tests) {
|
||||
XLOGF(ERR, "test: beginBarr {}, endBarr {}, hole {}", beginBarr, endBarr, hole);
|
||||
Path path = fmt::format("file-{}", folly::Random::rand32());
|
||||
folly::futures::Barrier barr1(16), barr2(16);
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
std::atomic_int failed(0);
|
||||
size_t offset = hole ? folly::Random::rand32(1 << 20, 8 << 20) : 0;
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
size_t length = folly::Random::rand32(128 << 10, 2 << 20);
|
||||
auto task = folly::coro::co_invoke(worker,
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
&failed,
|
||||
beginBarr ? &barr1 : nullptr,
|
||||
endBarr ? &barr2 : nullptr)
|
||||
.scheduleOn(&exec)
|
||||
.start();
|
||||
futures.push_back(std::move(task));
|
||||
offset += length;
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
|
||||
// todo: support check hole in future
|
||||
// if (!hole) {
|
||||
// if (beginBarr) {
|
||||
// CO_ASSERT_EQ(failed, 0);
|
||||
// }
|
||||
// CO_ASSERT_OK(co_await meta.open({SUPER_USER, path, {}, O_RDONLY}));
|
||||
// } else {
|
||||
// XLOGF(ERR, "{} close found hole", failed.load());
|
||||
// CO_ASSERT_NE(failed, 0);
|
||||
// CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, path, {}, O_RDONLY}), MetaCode::kFileHasHole);
|
||||
// }
|
||||
}
|
||||
|
||||
co_return;
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
409
tests/meta/store/ops/TestCreate.cc
Normal file
409
tests/meta/store/ops/TestCreate.cc
Normal file
@@ -0,0 +1,409 @@
|
||||
#include <array>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/kv/mem/MemKV.h"
|
||||
#include "common/kv/mem/MemKVEngine.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Path.h"
|
||||
#include "common/utils/RandomUtils.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "fdb/FDB.h"
|
||||
#include "fmt/core.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/ops/BatchOperation.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestCreate : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestCreate, KVTypes);
|
||||
|
||||
TYPED_TEST(TestCreate, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config cfg;
|
||||
cfg.mock_meta().set_check_file_hole(true);
|
||||
auto cluster = this->createMockCluster(cfg);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// create "file", should success
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644}));
|
||||
|
||||
// create "file" again should success
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644}));
|
||||
|
||||
// create file and open write without session
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "file2", {}, O_RDWR, p644}), StatusCode::kInvalidArg);
|
||||
|
||||
// create "file" again with or without O_EXCL
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "file", {}, O_EXCL, p644}), MetaCode::kExists);
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644}));
|
||||
|
||||
// other have no create permission, but can open files that already exists.
|
||||
flat::UserInfo otherUser(Uid(1), Gid(1), String());
|
||||
CO_ASSERT_OK(co_await meta.create({otherUser, "file", {}, O_RDONLY, p644}));
|
||||
CO_ASSERT_ERROR(co_await meta.create({otherUser, "file2", {}, O_RDONLY, p644}), MetaCode::kNoPermission);
|
||||
|
||||
// create under a directory that not exist
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "directory/file", {}, O_RDONLY, p644}), MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(
|
||||
co_await meta.create({SUPER_USER, PathAt(MetaTestHelper::randomInodeId(), "file"), {}, O_RDONLY, p644}),
|
||||
MetaCode::kNotFound);
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "/", {}, O_RDONLY, p644}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "a/", {}, O_RDONLY, p644}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "/a/b/.", {}, O_RDONLY, p644}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "/a/..", {}, O_RDONLY, p644}), StatusCode::kInvalidArg);
|
||||
|
||||
// create under directory
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "directory/sub-directory", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "directory/file", {}, O_EXCL, p644}));
|
||||
auto oresult = co_await meta.open({SUPER_USER, "directory", {}, O_DIRECTORY});
|
||||
CO_ASSERT_OK(oresult);
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(oresult->stat.id, "file2"), {}, O_EXCL, p644}));
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, "directory/sub-directory", {}, O_RDONLY, p644}),
|
||||
MetaCode::kIsDirectory);
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto result = co_await DirEntryList::snapshotLoad(*txn, InodeId::root(), "", 4096);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->entries.size(), 2); // file, directory
|
||||
for (size_t i = 0; i < result->entries.size(); i++) {
|
||||
fmt::print("{}/{} -> {}\n", result->entries.at(i).parent, result->entries.at(i).name, result->entries.at(i).id);
|
||||
}
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, TestSetGid) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto shareGroup = Gid(12345);
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "shared", Permission(S_ISGID | 0777), false}));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setPermission(SUPER_USER, "shared", AtFlags(), std::nullopt, shareGroup, std::nullopt)));
|
||||
auto dir = co_await meta.mkdirs({SUPER_USER, "shared/subdir1/subdir2", Permission(0070), true});
|
||||
CO_ASSERT_OK(dir);
|
||||
CO_ASSERT_TRUE(dir->stat.acl.perm & S_ISGID) << fmt::format("{:o}", dir->stat.acl.uid);
|
||||
|
||||
auto file = co_await meta.create({SUPER_USER, "shared/subdir1/subdir2/file", {}, O_RDONLY, Permission(0640)});
|
||||
CO_ASSERT_OK(file);
|
||||
CO_ASSERT_EQ(file->stat.acl.gid, shareGroup);
|
||||
CO_ASSERT_FALSE(file->stat.acl.perm & S_ISGID);
|
||||
|
||||
CO_ASSERT_OK(
|
||||
co_await meta.open({flat::UserInfo{Uid(1), shareGroup, ""}, "shared/subdir1/subdir2/file", {}, O_RDONLY}));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &store = cluster.meta().getStore();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
{
|
||||
auto path = "create-new-file";
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
InodeId inodeId;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = CreateReq(SUPER_USER, Path(path), session, O_RDWR, p644);
|
||||
auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
CO_ASSERT_OK(createResult);
|
||||
inodeId = createResult->stat.id;
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(InodeId::root()), // parent Inode
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path), // dir entry
|
||||
MetaTestHelper::getInodeKey(inodeId),
|
||||
std::string(kv::kMetadataVersionKey),
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path), // dir entry
|
||||
MetaTestHelper::getInodeKey(inodeId),
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(inodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(inodeId, session)),
|
||||
// todo: if per directory chain allocation enabled, should contains parent
|
||||
// MetaTestHelper::getInodeKey(InodeId::root())
|
||||
}),
|
||||
true);
|
||||
}
|
||||
|
||||
{
|
||||
auto path = "create-trunc-exists-file";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
InodeId inodeId;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = CreateReq(SUPER_USER, Path(path), session, O_TRUNC | O_WRONLY, p644);
|
||||
auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
CO_ASSERT_OK(createResult);
|
||||
inodeId = createResult->stat.id;
|
||||
},
|
||||
(std::vector<String>{std::string(kv::kMetadataVersionKey)}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(inodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(inodeId, session))
|
||||
}),
|
||||
true);
|
||||
}
|
||||
|
||||
// don't allow replace exists file on create
|
||||
// {
|
||||
// auto path = "create-trunc-replace-exists-file";
|
||||
// auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
// CO_ASSERT_OK(result);
|
||||
// auto oldInodeId = result->stat.id;
|
||||
// CO_ASSERT_OK(co_await meta.sync(
|
||||
// {SUPER_USER, oldInodeId, true, std::nullopt, std::nullopt, false, VersionedLength{2ULL << 30, 0}}));
|
||||
// auto session = MetaTestHelper::randomSession();
|
||||
// InodeId newInodeId;
|
||||
// CHECK_CONFLICT_SET(
|
||||
// [&](auto &txn) -> CoTask<void> {
|
||||
// auto req = CreateReq(SUPER_USER, Path(path), session, O_TRUNC | O_WRONLY, p644);
|
||||
// auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
// CO_ASSERT_OK(createResult);
|
||||
// newInodeId = createResult->stat.id;
|
||||
// CO_ASSERT_NE(oldInodeId, newInodeId);
|
||||
// },
|
||||
// (std::vector<String>{MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
// MetaTestHelper::getInodeKey(oldInodeId)}),
|
||||
// (std::vector<String>{MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
// MetaTestHelper::getInodeKey(newInodeId),
|
||||
// MetaTestHelper::getInodeSessionKey(FileSession::create(newInodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(newInodeId, session))}),
|
||||
// false);
|
||||
// }
|
||||
|
||||
{
|
||||
auto path = "create-open-exists-file";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY | O_CREAT, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = CreateReq(SUPER_USER, Path(path), {}, O_RDONLY | O_CREAT, p644);
|
||||
CO_ASSERT_OK(co_await BatchedOp::create(store, txn, req));
|
||||
},
|
||||
(std::vector<String>{std::string(kv::kMetadataVersionKey)}),
|
||||
(std::vector<String>{}),
|
||||
true);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, Concurrent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
// concurrent create on same path should conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
auto path = std::to_string(i) + ".txt";
|
||||
auto createTask = [&](auto &txn) -> CoTask<void> {
|
||||
auto req = CreateReq(SUPER_USER, Path(path), {}, O_RDONLY, p644);
|
||||
auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
CO_ASSERT_OK(createResult);
|
||||
};
|
||||
CO_ASSERT_CONFLICT(createTask, createTask);
|
||||
}
|
||||
|
||||
// concurrent create a different path is conflict because per directory chain allocation counter.
|
||||
// concurrent create on different path shouldn't conflict
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// auto create1 = [&](auto &txn) -> CoTask<void> {
|
||||
// auto req = CreateReq(SUPER_USER, Path(std::to_string(i) + ".1"), {}, O_RDONLY, p644);
|
||||
// auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
// CO_ASSERT_OK(createResult);
|
||||
// };
|
||||
// auto create2 = [&](auto &txn) -> CoTask<void> {
|
||||
// auto req = CreateReq(SUPER_USER, Path(std::to_string(i) + ".2"), {}, O_RDONLY, p644);
|
||||
// auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
// CO_ASSERT_OK(createResult);
|
||||
// };
|
||||
// CO_ASSERT_NO_CONFLICT(create1, create2);
|
||||
// }
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, FaultInjection) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, "dirA/dirB/dirC", p755, true});
|
||||
CO_ASSERT_OK(result);
|
||||
std::vector<Path> parents =
|
||||
{"/", "/dirA", "/dirA/dirB", "/dirA/dirB/dirC", "/dirA/dirB/../", "/dirA/dirB/../dirB", "not_exists"};
|
||||
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
FAULT_INJECTION_SET(10, 3); // 10%, 3 faults.
|
||||
auto parent = parents[folly::Random::rand32(parents.size())];
|
||||
auto path = parent;
|
||||
path.append(std::to_string(i));
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
if (parent == "not_exists") {
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
} else {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, OTrunc) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config cfg;
|
||||
cfg.mock_meta().set_check_file_hole(true);
|
||||
auto cluster = this->createMockCluster(cfg);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto fname = fmt::format("truncate-inplace-{}", i);
|
||||
auto create = co_await meta.create({SUPER_USER, fname, {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
CO_ASSERT_OK(co_await meta.sync(
|
||||
{SUPER_USER, create->stat.id, true, std::nullopt, std::nullopt, false, VersionedLength{1_MB, 0}}));
|
||||
|
||||
auto createAgain = co_await meta.create({SUPER_USER, fname, MetaTestHelper::randomSession(), O_RDWR, p644});
|
||||
CO_ASSERT_OK(createAgain);
|
||||
CO_ASSERT_FALSE(createAgain->needTruncate);
|
||||
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto open = co_await meta.create({SUPER_USER, fname, session, O_TRUNC | O_RDWR, p644});
|
||||
CO_ASSERT_OK(open);
|
||||
CO_ASSERT_EQ(open->stat.id, create->stat.id);
|
||||
CO_ASSERT_TRUE(open->needTruncate);
|
||||
CO_ASSERT_NE(open->stat.asFile().length, 0);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, EventLog) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
FAULT_INJECTION_SET(10, 2);
|
||||
auto create = co_await meta.create({SUPER_USER, fmt::format("{}", i), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
// create again, shouldn't have log.
|
||||
FAULT_INJECTION_SET(10, 2);
|
||||
auto create = co_await meta.create({SUPER_USER, fmt::format("{}", i), {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
}
|
||||
|
||||
// todo: how to verify log counts here?
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestCreate, batch) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
auto &engine = *this->kvEngine();
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto dir1 = (co_await meta.mkdirs({SUPER_USER, fmt::format("{}.1", i), p755, false}))->stat.id;
|
||||
auto dir2 = (co_await meta.mkdirs({SUPER_USER, fmt::format("{}.2", i), p755, false}))->stat.id;
|
||||
std::vector<std::pair<CreateReq, Result<CreateRsp>>> reqs;
|
||||
auto cnts = folly::Random::rand32(200);
|
||||
for (auto i = 0u; i < cnts; i++) {
|
||||
auto path = folly::Random::rand32(10);
|
||||
auto user = folly::Random::oneIn(2) ? SUPER_USER : flat::UserInfo{Uid(1), Gid(1)};
|
||||
auto perm = folly::Random::rand32(0777);
|
||||
auto flags = folly::Random::rand32(O_RDWR + 1);
|
||||
for (auto flag : {O_CREAT, O_TRUNC, O_EXCL, O_DIRECTORY}) {
|
||||
if (folly::Random::oneIn(2)) {
|
||||
flags |= flag;
|
||||
}
|
||||
}
|
||||
FAULT_INJECTION_SET(5, 3);
|
||||
CreateReq req{user,
|
||||
PathAt(dir1, folly::to<std::string>(path)),
|
||||
flags != O_RDONLY ? MetaTestHelper::randomSession() : std::optional<SessionInfo>(),
|
||||
OpenFlags(flags),
|
||||
Permission(perm)};
|
||||
auto rsp = co_await meta.create(req);
|
||||
reqs.push_back({req, rsp});
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<BatchedOp::Waiter<CreateReq, CreateRsp>>> waiters;
|
||||
auto batch = std::make_unique<BatchedOp>(store, dir2);
|
||||
for (auto &[req, rsp] : reqs) {
|
||||
auto req1 = req;
|
||||
req1.path.parent = dir2;
|
||||
auto waiter = std::make_unique<BatchedOp::Waiter<CreateReq, CreateRsp>>(req1);
|
||||
batch->add(*waiter);
|
||||
waiters.emplace_back(std::move(waiter));
|
||||
}
|
||||
|
||||
// Note: test can't pass with fault injection enabled, result may change if inject MaybeCommtted error.
|
||||
// FAULT_INJECTION_SET(5, 3);
|
||||
auto txn = engine.createReadWriteTransaction();
|
||||
auto driver = OperationDriver(*batch, Void{});
|
||||
CO_ASSERT_OK(co_await driver.run(std::move(txn), {}, false, false));
|
||||
|
||||
for (auto i = 0u; i < reqs.size(); i++) {
|
||||
auto req = reqs[i].first;
|
||||
auto expected = reqs[i].second;
|
||||
auto result = waiters[i]->getResult();
|
||||
if (expected.hasError()) {
|
||||
CO_ASSERT_TRUE(result.hasError())
|
||||
<< fmt::format("req {} expected {}, result {}", req, expected.error(), result.value());
|
||||
CO_ASSERT_EQ(result.error().code(), expected.error().code());
|
||||
CO_ASSERT_NE(result.error().code(), MetaCode::kFoundBug);
|
||||
} else {
|
||||
CO_ASSERT_FALSE(result.hasError())
|
||||
<< fmt::format("req {} expected {}, result {}", req, expected.value(), result.error());
|
||||
CO_ASSERT_EQ(expected->stat.acl, result->stat.acl);
|
||||
CO_ASSERT_EQ(expected->needTruncate, result->needTruncate);
|
||||
CO_ASSERT_FALSE((expected->needTruncate && !req.flags.contains(O_TRUNC)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
106
tests/meta/store/ops/TestHardLink.cc
Normal file
106
tests/meta/store/ops/TestHardLink.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/kv/mem/MemKV.h"
|
||||
#include "common/kv/mem/MemKVEngine.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Path.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/String.h"
|
||||
#include "fdb/FDB.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
template <typename KV>
|
||||
class TestHardLink : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestHardLink, KVTypes);
|
||||
|
||||
TYPED_TEST(TestHardLink, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
FAULT_INJECTION_SET(10, 5);
|
||||
|
||||
auto createResult = co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(createResult);
|
||||
auto &file = createResult->stat;
|
||||
|
||||
auto mkdirResult = co_await meta.mkdirs({SUPER_USER, "directory", p755, true});
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
auto &dir = mkdirResult->stat;
|
||||
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, PathAt("symlink-file"), "file"}));
|
||||
auto symlinkFileResult = co_await meta.stat({SUPER_USER, PathAt("symlink-file"), AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(symlinkFileResult);
|
||||
auto &symlinkFile = symlinkFileResult->stat;
|
||||
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, PathAt("symlink-dir"), "directory"}));
|
||||
auto symlinkDirResult = co_await meta.stat({SUPER_USER, PathAt("symlink-dir"), AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(symlinkDirResult);
|
||||
auto &symlinkDir = symlinkDirResult->stat;
|
||||
|
||||
using TestArg = std::tuple<std::string /* oldpath*/,
|
||||
std::string /* newpath*/,
|
||||
bool /* follow */,
|
||||
std::optional<Status> /* error */,
|
||||
InodeId /* target */,
|
||||
uint16_t /* nlink*/>;
|
||||
for (auto [oldPath, newPath, follow, error, targetId, nlink] :
|
||||
{TestArg("file", "file-hardlink1", false, {}, file.id, 2),
|
||||
TestArg("file", "file-hardlink2", false, {}, file.id, 3),
|
||||
TestArg("directory", "directory-hardlink", false, MetaCode::kIsDirectory, dir.id, 1),
|
||||
TestArg("symlink-file", "symlink-file-hardlink-follow", true, {}, file.id, 4),
|
||||
TestArg("symlink-file", "symlink-file-hardlink-no-follow", false, {}, symlinkFile.id, 2),
|
||||
TestArg("symlink-dir", "symlink-dir-hardlink-follow", true, MetaCode::kIsDirectory, dir.id, 1),
|
||||
TestArg("symlink-dir", "symlink-dir-hardlink-no-follow", false, {}, symlinkDir.id, 2)}) {
|
||||
fmt::print("hardlink {} -> {}, follow {}\n", oldPath, newPath, follow);
|
||||
auto result = co_await meta.hardLink(
|
||||
{SUPER_USER, oldPath, newPath, follow ? AtFlags(AT_SYMLINK_FOLLOW) : AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
if (error) {
|
||||
CO_ASSERT_ERROR(result, error->code());
|
||||
continue;
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
auto &inode = result->stat;
|
||||
CO_ASSERT_EQ(inode.id, targetId);
|
||||
CO_ASSERT_EQ(inode.nlink, nlink);
|
||||
}
|
||||
|
||||
FAULT_INJECTION_SET(0, 0);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file", AtFlags(), false}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file-hardlink1", AtFlags(), false}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "symlink-file-hardlink-follow", AtFlags(), false}));
|
||||
auto result = co_await meta.stat({SUPER_USER, "file-hardlink2", AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
auto &stat = result->stat;
|
||||
CO_ASSERT_EQ(stat.nlink, 1);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file-hardlink2", AtFlags(), false}));
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, "file-hardlink2", AtFlags(AT_SYMLINK_NOFOLLOW)}),
|
||||
MetaCode::kNotFound);
|
||||
}());
|
||||
}
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
130
tests/meta/store/ops/TestIFlags.cc
Normal file
130
tests/meta/store/ops/TestIFlags.cc
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <linux/fs.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/String.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/Utils.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestIFlags : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestIFlags, KVTypes);
|
||||
|
||||
TYPED_TEST(TestIFlags, FS_HUGE_FILE_FL) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto ua = flat::UserInfo{flat::Uid(1), flat::Gid(1), String()};
|
||||
auto ub = flat::UserInfo{flat::Uid(2), flat::Gid(2), String()};
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({ua, "dir/file_ua", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ua, "dir/file_ua", IFlags(FS_HUGE_FILE_FL))));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(SUPER_USER, "dir/file_ua", IFlags())));
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setIFlags(ub, "dir/file_ua", IFlags(FS_HUGE_FILE_FL))),
|
||||
MetaCode::kNoPermission);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestIFlags, FS_IMMUTABLE_FL) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto ua = flat::UserInfo{flat::Uid(1), flat::Gid(1), String()};
|
||||
auto ub = flat::UserInfo{flat::Uid(2), flat::Gid(2), String()};
|
||||
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "protected-file", {}, 0, p644}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir/protected-dir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/file", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({ua, "dir/file_ua", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/protected-file", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/protected-dir/file", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/protected-dir/protected-file", {}, 0, p644}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir/normal-dir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir/normal-dir2", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.hardLink(
|
||||
{SUPER_USER, "dir/protected-file", "dir/normal-dir/protected-file", AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
CO_ASSERT_OK(co_await meta.hardLink(
|
||||
{SUPER_USER, "dir/protected-file", "dir/normal-dir2/protected-file", AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ua, "dir/file_ua", IFlags())));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ub, "dir/file_ua", IFlags())));
|
||||
config.mock_meta().set_allow_owner_change_immutable(false);
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setIFlags(ua, "dir/file_ua", IFlags(FS_IMMUTABLE_FL))),
|
||||
MetaCode::kNoPermission);
|
||||
config.mock_meta().set_allow_owner_change_immutable(true);
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ua, "dir/file_ua", IFlags(FS_IMMUTABLE_FL))));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ub, "dir/file_ua", IFlags(FS_IMMUTABLE_FL))));
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setIFlags(ub, "dir/file_ua", IFlags())), MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(ua, "dir/file_ua", IFlags())));
|
||||
|
||||
std::vector<InodeId> inodes;
|
||||
for (auto &path : std::vector<std::string>{"protected-file",
|
||||
"dir/protected-dir",
|
||||
"dir/protected-file",
|
||||
"dir/protected-dir/protected-file"}) {
|
||||
auto res = co_await meta.setAttr(SetAttrReq::setIFlags(SUPER_USER, path, IFlags(FS_IMMUTABLE_FL)));
|
||||
CO_ASSERT_OK(res);
|
||||
inodes.push_back(res->stat.id);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, path, AtFlags(0), true}), MetaCode::kNoPermission);
|
||||
}
|
||||
auto res = co_await meta.stat({SUPER_USER, "dir/protected-dir/file", AtFlags()});
|
||||
CO_ASSERT_OK(res);
|
||||
inodes.push_back(res->stat.id);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file", AtFlags(0), true}));
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, "dir", AtFlags(0), true}), MetaCode::kNoPermission);
|
||||
config.mock_meta().set_recursive_remove_perm_check(0);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "dir", AtFlags(0), true}));
|
||||
co_await folly::coro::sleep(std::chrono::seconds(3));
|
||||
|
||||
for (auto &inodeId : inodes) {
|
||||
CO_ASSERT_INODE_EXISTS(inodeId);
|
||||
}
|
||||
|
||||
CO_ASSERT_OK(co_await printTree(meta));
|
||||
co_return;
|
||||
}());
|
||||
}
|
||||
} // namespace hf3fs::meta::server
|
||||
81
tests/meta/store/ops/TestList.cc
Normal file
81
tests/meta/store/ops/TestList.cc
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <algorithm>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestList : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestList, KVTypes);
|
||||
|
||||
TYPED_TEST(TestList, FaultInjection) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
std::vector<std::string> paths;
|
||||
size_t numFiles = folly::Random::rand32(0, 4096);
|
||||
for (size_t i = 0; i < numFiles; i++) {
|
||||
std::string path = std::to_string(i);
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
paths.push_back(path);
|
||||
}
|
||||
std::sort(paths.begin(), paths.end());
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
FAULT_INJECTION_SET(10, 3); // 10%, 3 faults
|
||||
std::vector<DirEntry> entries;
|
||||
bool hasMore = true;
|
||||
const bool needStatus = folly::Random::oneIn(2);
|
||||
String prev;
|
||||
|
||||
// do load
|
||||
while (hasMore) {
|
||||
auto limit = folly::Random::rand32(0, 512);
|
||||
auto req = i % 2 == 0 ? ListReq(SUPER_USER, Path("/"), prev, (int)limit, needStatus)
|
||||
: ListReq(SUPER_USER, InodeId::root(), prev, (int)limit, needStatus);
|
||||
auto list = co_await meta.list(req);
|
||||
CO_ASSERT_OK(list);
|
||||
if (needStatus) {
|
||||
CO_ASSERT_TRUE(list->inodes.size() == list->entries.size());
|
||||
}
|
||||
for (size_t i = 0; i < list->entries.size(); i++) {
|
||||
if (needStatus) {
|
||||
auto entry = list->entries.at(i);
|
||||
auto inode = list->inodes.at(i);
|
||||
entries.push_back(entry);
|
||||
CO_ASSERT_EQ(entry.id, inode.id);
|
||||
} else {
|
||||
entries.push_back(list->entries.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
hasMore = list->more;
|
||||
if (list->entries.size() == 0) {
|
||||
CO_ASSERT_FALSE(hasMore);
|
||||
}
|
||||
if (hasMore) {
|
||||
prev = list->entries.at(list->entries.size() - 1).name;
|
||||
}
|
||||
}
|
||||
|
||||
// check
|
||||
CO_ASSERT_EQ(entries.size(), numFiles);
|
||||
for (size_t i = 0; i < numFiles; i++) {
|
||||
auto entry = entries[i];
|
||||
CO_ASSERT_EQ(entry.name, paths[i]);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
} // namespace hf3fs::meta::server
|
||||
152
tests/meta/store/ops/TestMkdirs.cc
Normal file
152
tests/meta/store/ops/TestMkdirs.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <fcntl.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestMkdirs : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestMkdirs, KVTypes);
|
||||
|
||||
TYPED_TEST(TestMkdirs, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "a", p755, false}));
|
||||
CO_ASSERT_ERROR(co_await meta.mkdirs({SUPER_USER, "b/c", p755, false}), MetaCode::kNotFound);
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "b/c", p755, true}));
|
||||
|
||||
for (auto path : {"a", "b", "b/c"}) {
|
||||
auto result = co_await meta.open({SUPER_USER, path, {}, O_DIRECTORY});
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, "b/c", p755, true});
|
||||
CO_ASSERT_ERROR(result, MetaCode::kExists);
|
||||
auto result2 = co_await meta.mkdirs({SUPER_USER, "/", p700, true});
|
||||
CO_ASSERT_ERROR(result2, MetaCode::kExists);
|
||||
|
||||
flat::UserInfo otherUser(Uid(1), Gid(1), String());
|
||||
CO_ASSERT_ERROR(co_await meta.mkdirs({otherUser, "d/e", p755, true}), MetaCode::kNoPermission);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(SUPER_USER, "/", IFlags(FS_CHAIN_ALLOCATION_FL))));
|
||||
auto result3 = co_await meta.stat({SUPER_USER, "/", AtFlags()});
|
||||
CO_ASSERT_OK(result3);
|
||||
CO_ASSERT_TRUE(result3->stat.acl.iflags & FS_CHAIN_ALLOCATION_FL);
|
||||
|
||||
auto result4 = co_await meta.mkdirs({SUPER_USER, "f/g", p755, true});
|
||||
CO_ASSERT_OK(result4);
|
||||
CO_ASSERT_TRUE(result4->stat.acl.iflags & FS_CHAIN_ALLOCATION_FL);
|
||||
CO_ASSERT_EQ(result4->stat.asDirectory().chainAllocCounter, -1);
|
||||
|
||||
auto dir = result4->stat.id;
|
||||
auto result5 = co_await meta.create(CreateReq(SUPER_USER, PathAt(dir, "a"), std::nullopt, OpenFlags(), p755));
|
||||
CO_ASSERT_OK(result5);
|
||||
CO_ASSERT_EQ(result5->stat.id.useNewChunkEngine(), cluster.config().mock_meta().enable_new_chunk_engine());
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setIFlags(SUPER_USER, dir, IFlags(FS_NEW_CHUNK_ENGINE))));
|
||||
auto result6 = co_await meta.create(CreateReq(SUPER_USER, PathAt(dir, "b"), std::nullopt, OpenFlags(), p755));
|
||||
CO_ASSERT_OK(result6);
|
||||
CO_ASSERT_TRUE(result6->stat.id.useNewChunkEngine());
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestMkdirs, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string path = std::to_string(i) + ".txt";
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = MkdirsReq(SUPER_USER, Path(path), p755, false);
|
||||
auto createResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(createResult);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(InodeId::root()), // parent Inode
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path), // dir entry
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path), // dir entry
|
||||
}),
|
||||
false);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestMkdirs, Concurrent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
// concurrent mkdir on same path should conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string path = std::to_string(i) + ".dir";
|
||||
auto mkdir = [&](auto &txn) -> CoTask<void> {
|
||||
auto req = MkdirsReq(SUPER_USER, Path(path), p755, false);
|
||||
auto mkdirResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
};
|
||||
CO_ASSERT_CONFLICT(mkdir, mkdir);
|
||||
}
|
||||
|
||||
// concurrent mkdir on different path shouldn't conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
auto mkdir1 = [&](auto &txn) -> CoTask<void> {
|
||||
auto req = MkdirsReq(SUPER_USER, Path(std::to_string(i) + ".1"), p755, false);
|
||||
auto mkdirResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
};
|
||||
auto mkdir2 = [&](auto &txn) -> CoTask<void> {
|
||||
auto req = MkdirsReq(SUPER_USER, Path(std::to_string(i) + ".2"), p755, false);
|
||||
auto mkdirResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
};
|
||||
CO_ASSERT_NO_CONFLICT(mkdir1, mkdir2);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestMkdirs, FaultInjection) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, "dirA/dirB/dirC", p755, true});
|
||||
CO_ASSERT_OK(result);
|
||||
std::vector<Path> parents =
|
||||
{"/", "/dirA", "/dirA/dirB", "/dirA/dirB/dirC", "/dirA/dirB/../", "/dirA/dirB/../dirB", "not_exists"};
|
||||
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
FAULT_INJECTION_SET(5, 3); // 5%, 3 faults.
|
||||
auto parent = parents[folly::Random::rand32(parents.size())];
|
||||
auto path = parent;
|
||||
path.append(std::to_string(i));
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, path, p755, false});
|
||||
if (parent == "not_exists") {
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
} else {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
343
tests/meta/store/ops/TestOpen.cc
Normal file
343
tests/meta/store/ops/TestOpen.cc
Normal file
@@ -0,0 +1,343 @@
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "fmt/core.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestOpen : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestOpen, KVTypes);
|
||||
|
||||
TYPED_TEST(TestOpen, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// create file, directory and symlink
|
||||
CO_ASSERT_OK(
|
||||
co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644, std::nullopt, true /* dynamic stripe */}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "symlink", "file"}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir/dir2", p700, true}));
|
||||
|
||||
// open file and symlink should success, and both point to file
|
||||
auto result1 = co_await meta.open({SUPER_USER, "file", {}, O_RDONLY});
|
||||
auto result2 = co_await meta.open({SUPER_USER, "symlink", {}, O_RDONLY});
|
||||
CO_ASSERT_OK(result1);
|
||||
CO_ASSERT_OK(result2);
|
||||
CO_ASSERT_EQ(result1->stat, result2->stat);
|
||||
|
||||
// open with InodeId
|
||||
auto result3 = co_await meta.open({SUPER_USER, result1->stat.id, {}, O_RDONLY});
|
||||
CO_ASSERT_OK(result3);
|
||||
CO_ASSERT_EQ(result1->stat, result3->stat);
|
||||
CO_ASSERT_NE(result3->stat.asFile().dynStripe, 0);
|
||||
|
||||
auto rw1 = co_await meta.open(
|
||||
{SUPER_USER, result1->stat.id, MetaTestHelper::randomSession(), O_RDWR, true /* dynamic stripe */});
|
||||
CO_ASSERT_OK(rw1);
|
||||
CO_ASSERT_NE(rw1->stat.asFile().dynStripe, 0);
|
||||
|
||||
auto rw2 = co_await meta.open(
|
||||
{SUPER_USER, result1->stat.id, MetaTestHelper::randomSession(), O_RDWR, false /* dynamic stripe */});
|
||||
CO_ASSERT_OK(rw2);
|
||||
CO_ASSERT_EQ(rw2->stat.asFile().dynStripe, 0);
|
||||
|
||||
// open a not exists file, should return MetaCode::kNotFound
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "not-exist", {}, O_RDONLY}), MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "not-exist/not-exist", {}, O_RDONLY}), MetaCode::kNotFound);
|
||||
|
||||
flat::UserInfo otherUser(Uid(1), Gid(1), String());
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
// other user should can only open file in read only mode
|
||||
CO_ASSERT_OK(co_await meta.open({otherUser, "file", {}, O_RDONLY}));
|
||||
CO_ASSERT_ERROR(co_await meta.open({otherUser, "file", session, O_RDWR}), MetaCode::kNoPermission);
|
||||
|
||||
// open not exists file under directory, su should get kNotFound, other user should get kNoPermission
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "dir/file", {}, O_RDONLY}), MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.open({otherUser, "dir/file", {}, O_RDONLY}), MetaCode::kNoPermission);
|
||||
|
||||
// open file with O_DIRECTORY should get kNotDirectory, write open directory should get kIsDirectory
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "file", {}, O_RDONLY | O_DIRECTORY}), MetaCode::kNotDirectory);
|
||||
CO_ASSERT_OK(co_await meta.open({SUPER_USER, "dir/dir2", {}, O_RDONLY | O_DIRECTORY}));
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "dir/dir2", session, O_WRONLY}), MetaCode::kIsDirectory);
|
||||
|
||||
// open without session
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "dir/dir2", {}, O_WRONLY}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "dir/dir2", {}, O_RDWR}), StatusCode::kInvalidArg);
|
||||
|
||||
config.mock_meta().set_readonly(true);
|
||||
CO_ASSERT_OK(co_await meta.open({otherUser, "file", {}, O_RDONLY}));
|
||||
CO_ASSERT_ERROR(co_await meta.open({SUPER_USER, "file", session, O_WRONLY}), StatusCode::kReadOnlyMode);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestOpen, Concurrent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
auto path = "open-file-with-write";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
InodeId inodeId = result->stat.id;
|
||||
|
||||
fmt::print("{}\n", result->stat);
|
||||
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = OpenReq(SUPER_USER, Path(path), session, O_RDWR);
|
||||
CO_ASSERT_OK(co_await store.open(req)->run(txn));
|
||||
},
|
||||
(std::vector<String>{}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(inodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(inodeId, session)),
|
||||
}),
|
||||
true);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestOpen, FaultInjection) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
std::vector<std::string> paths;
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
std::string path = std::to_string(i);
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
paths.push_back("not_exists");
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
FAULT_INJECTION_SET(10, 3); // 10%, 3 faults
|
||||
auto path = paths[folly::Random::rand32(paths.size())];
|
||||
auto result = co_await meta.open({SUPER_USER, path, {}, O_RDONLY});
|
||||
if (path == "not_exists") {
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
} else {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
// TYPED_TEST(TestOpen, PruneSession) {
|
||||
// folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// MockCluster::Config cfg;
|
||||
// cfg.mock_meta().set_check_file_hole(true);
|
||||
// auto cluster = this->createMockCluster(cfg);
|
||||
// auto &meta = cluster.meta().getOperator();
|
||||
// auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
// std::vector<std::string> path;
|
||||
// Uuid clientId = Uuid::random();
|
||||
// std::vector<Uuid> sessions;
|
||||
// std::vector<std::pair<InodeId, uint64_t>> inodes;
|
||||
// for (auto i = 0; i < 20; i++) {
|
||||
// std::string path = std::to_string(i);
|
||||
// auto sessionId = Uuid::random();
|
||||
// auto result = co_await meta.create({SUPER_USER, path, SessionInfo(ClientId(clientId), sessionId), O_RDWR,
|
||||
// p644}); CO_ASSERT_OK(result);
|
||||
|
||||
// // write to make a hole
|
||||
// auto offset = folly::Random::rand32(1 << 20, 4 << 20);
|
||||
// auto length = folly::Random::rand32(2 << 20) + 1;
|
||||
// co_await randomWrite(meta, storage, result->stat, offset, length);
|
||||
// sessions.push_back(sessionId);
|
||||
// inodes.push_back({result->stat.id, offset + length});
|
||||
// }
|
||||
// for (auto i = 0; i < 100; i++) {
|
||||
// sessions.push_back(Uuid::random());
|
||||
// }
|
||||
// std::shuffle(sessions.begin(), sessions.end(), std::mt19937());
|
||||
// CO_ASSERT_OK(co_await meta.pruneSession({ClientId(clientId), sessions}));
|
||||
|
||||
// co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
// for (auto [inodeId, length] : inodes) {
|
||||
// auto result = co_await meta.stat({SUPER_USER, inodeId, AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
// CO_ASSERT_OK(result);
|
||||
// auto stat = result->stat;
|
||||
// CO_ASSERT_EQ(stat.asFile().length, length);
|
||||
// CO_ASSERT_TRUE(stat.asFile().hasHole());
|
||||
// }
|
||||
// }());
|
||||
// }
|
||||
|
||||
TYPED_TEST(TestOpen, OTrunc) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config cfg;
|
||||
cfg.mock_meta().set_check_file_hole(true);
|
||||
auto cluster = this->createMockCluster(cfg);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto fname = fmt::format("truncate-inplace-{}", i);
|
||||
auto create = co_await meta.create({SUPER_USER, fname, {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
co_await truncate(meta, storage, create->stat, 1_MB);
|
||||
|
||||
FAULT_INJECTION_SET(20, 3);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto open = co_await meta.open({SUPER_USER, fname, session, O_TRUNC | O_RDWR});
|
||||
CO_ASSERT_EQ(open->stat.id, create->stat.id);
|
||||
CO_ASSERT_TRUE(open->needTruncate);
|
||||
CO_ASSERT_NE(open->stat.asFile().length, 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto fname = fmt::format("truncate-by-inode-{}", i);
|
||||
auto create = co_await meta.create({SUPER_USER, fname, {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
co_await truncate(meta, storage, create->stat, 2_GB);
|
||||
|
||||
FAULT_INJECTION_SET(20, 3);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto open = co_await meta.open({SUPER_USER, create->stat.id, session, O_TRUNC | O_RDWR});
|
||||
CO_ASSERT_EQ(open->stat.id, create->stat.id);
|
||||
CO_ASSERT_TRUE(open->needTruncate);
|
||||
CO_ASSERT_NE(open->stat.asFile().length, 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto fname = fmt::format("truncate-replace-{}", i);
|
||||
auto create = co_await meta.create({SUPER_USER, fname, {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
CO_ASSERT_EQ(create->stat.asFile().dynStripe, 0);
|
||||
co_await truncate(meta, storage, create->stat, 2_GB);
|
||||
|
||||
auto session1 = MetaTestHelper::randomSession();
|
||||
auto session2 = MetaTestHelper::randomSession();
|
||||
auto session3 = MetaTestHelper::randomSession();
|
||||
auto open1 = co_await meta.open({SUPER_USER, fname, session1, O_TRUNC | O_RDWR, true /* dynamic stripe */});
|
||||
CO_ASSERT_NE(open1->stat.id, create->stat.id);
|
||||
CO_ASSERT_NE(open1->stat.asFile().dynStripe, 0);
|
||||
|
||||
auto open2 = co_await meta.open({SUPER_USER, fname, session2, O_TRUNC | O_RDWR});
|
||||
CO_ASSERT_EQ(open1->stat.id, open2->stat.id);
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, open1->stat.id, AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, 0);
|
||||
|
||||
co_await truncate(meta, storage, open1->stat, 2_GB);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, open1->stat.id, session1, true, {}, {}}));
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, open1->stat.id, session2, true, {}, {}}));
|
||||
|
||||
auto open3 = co_await meta.open({SUPER_USER, fname, session3, O_TRUNC | O_RDWR});
|
||||
CO_ASSERT_NE(open3->stat.id, open1->stat.id);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestOpen, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &store = cluster.meta().getStore();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
{
|
||||
auto path = "open-trunc-exists-file";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
InodeId inodeId = result->stat.id;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = OpenReq(SUPER_USER, Path(path), session, O_TRUNC | O_WRONLY);
|
||||
auto openResult = co_await store.open(req)->run(txn);
|
||||
CO_ASSERT_OK(openResult);
|
||||
CO_ASSERT_EQ(inodeId, openResult->stat.id);
|
||||
inodeId = openResult->stat.id;
|
||||
},
|
||||
(std::vector<String>{}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(inodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(inodeId, session))
|
||||
}),
|
||||
true);
|
||||
}
|
||||
|
||||
{
|
||||
auto path = "open-trunc-file-by-inode";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto inodeId = result->stat.id;
|
||||
co_await truncate(meta, storage, result->stat, 2_GB);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = OpenReq(SUPER_USER, inodeId, session, O_TRUNC | O_WRONLY);
|
||||
auto openResult = co_await store.open(req)->run(txn);
|
||||
CO_ASSERT_OK(openResult);
|
||||
CO_ASSERT_EQ(openResult->stat.id, inodeId);
|
||||
},
|
||||
(std::vector<String>{}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(inodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(inodeId, session))
|
||||
}),
|
||||
false);
|
||||
}
|
||||
|
||||
{
|
||||
auto path = "open-trunc-replace-exists-file";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto oldInodeId = result->stat.id;
|
||||
co_await truncate(meta, storage, result->stat, 2_GB);
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
InodeId newInodeId;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = OpenReq(SUPER_USER, Path(path), session, O_TRUNC | O_WRONLY);
|
||||
auto openResult = co_await store.open(req)->run(txn);
|
||||
CO_ASSERT_OK(openResult);
|
||||
newInodeId = openResult->stat.id;
|
||||
CO_ASSERT_NE(oldInodeId, newInodeId);
|
||||
},
|
||||
(std::vector<String>{MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
MetaTestHelper::getInodeKey(oldInodeId)}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
MetaTestHelper::getInodeKey(newInodeId),
|
||||
MetaTestHelper::getInodeSessionKey(FileSession::create(newInodeId, session)),
|
||||
// MetaTestHelper::getClientSessionKey(FileSession::create(newInodeId, session))
|
||||
}),
|
||||
false);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
97
tests/meta/store/ops/TestRealPath.cc
Normal file
97
tests/meta/store/ops/TestRealPath.cc
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <fcntl.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Path.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/PathResolve.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestRealPath : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestRealPath, KVTypes);
|
||||
|
||||
TYPED_TEST(TestRealPath, basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, InodeId::root(), true}))->path, "/");
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "/a/b/c", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "/a/b/c/file", {}, O_RDONLY, p777}));
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/.", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/..", true}))->path, "/");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/../../..", true}))->path, "/");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/b", true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "/a/b/c", true}))->path, "/a/b/c");
|
||||
|
||||
auto b = (co_await meta.stat({SUPER_USER, "/a/b", AtFlags()}))->stat.id;
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "."), false}))->path, ".");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "."), true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "c"), true}))->path, "/a/b/c");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "c/.."), false}))->path, ".");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "c/.."), true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, ".."), true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "c/file"), false}))->path, "c/file");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(b, "c/file"), true}))->path, "/a/b/c/file");
|
||||
|
||||
auto file = (co_await meta.stat({SUPER_USER, "/a/b/c/file", AtFlags()}))->stat.id;
|
||||
CO_ASSERT_ERROR((co_await meta.getRealPath({SUPER_USER, file, true})), MetaCode::kNotDirectory);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/symlink-1", "/a/b"}));
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/.", false}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/.", true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/c", true}))->path, "/a/b/c");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/c/..", false}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/c/..", true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/..", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/c/file", false}))->path, "/a/b/c/file");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "symlink-1/c/file", true}))->path, "/a/b/c/file");
|
||||
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "a/symlink-2", "b"}));
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/.", false}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/..", false}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/.", true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/..", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/c", true}))->path, "/a/b/c");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/c/..", false}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/c/..", true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/..", true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/c/file", false}))->path, "/a/b/c/file");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, "a/symlink-2/c/file", true}))->path, "/a/b/c/file");
|
||||
|
||||
auto a = (co_await meta.stat({SUPER_USER, "/a", AtFlags()}))->stat.id;
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/."), false}))->path, "b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/.."), false}))->path, ".");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/."), true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/c"), true}))->path, "/a/b/c");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/.."), true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/c/.."), false}))->path, "b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/c/.."), true}))->path, "/a/b");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/.."), true}))->path, "/a");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/c/file"), false}))->path, "b/c/file");
|
||||
CO_ASSERT_EQ((co_await meta.getRealPath({SUPER_USER, PathAt(a, "symlink-2/c/file"), true}))->path, "/a/b/c/file");
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
397
tests/meta/store/ops/TestRemove.cc
Normal file
397
tests/meta/store/ops/TestRemove.cc
Normal file
@@ -0,0 +1,397 @@
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/Utils.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestRemove : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestRemove, KVTypes);
|
||||
|
||||
TYPED_TEST(TestRemove, GC) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
config.mock_meta().gc().set_scan_interval(10_ms);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &gcManager = cluster.meta().getGcManager();
|
||||
|
||||
int fileCnt = 512;
|
||||
|
||||
// create a directory, create some files, subdirectories, symlinks under it, then remove it recursively
|
||||
auto mkdirResult = co_await meta.mkdirs({SUPER_USER, "directory", p755, false});
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
auto &directory = mkdirResult->stat;
|
||||
for (int i = 0; i < fileCnt; i++) {
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(directory.id, std::to_string(i) + ".file"), {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, PathAt(directory.id, std::to_string(i) + ".dir"), p755, false}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, PathAt(directory.id, std::to_string(i) + ".symlink"), "target"}));
|
||||
}
|
||||
|
||||
GET_INODE_CNTS(inodes);
|
||||
GET_DIRENTRY_CNTS(entries);
|
||||
fmt::print("inodes {} dirEntries {}\n", inodes, entries);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "directory", AtFlags(), true}));
|
||||
|
||||
GET_INODE_CNTS(numInodes);
|
||||
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(4));
|
||||
|
||||
READ_WRITE_TRANSACTION_NO_COMMIT({
|
||||
for (auto gcDir : gcManager.currGcDirectories()) {
|
||||
auto empty = co_await DirEntryList::checkEmpty(*txn, gcDir->dirId());
|
||||
CO_ASSERT_OK(empty);
|
||||
CO_ASSERT_TRUE(empty.value());
|
||||
}
|
||||
|
||||
// all inodes except root, gcRoot, gcDirectory should be removed
|
||||
CO_ASSERT_INODE_CNTS(numInodes - 1 - fileCnt * 3);
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, Remove) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
FAULT_INJECTION_SET(10, 3);
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, "not-exists", AtFlags(), false}), MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, "/", AtFlags(), false}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, "not-exists/", AtFlags(), false}), StatusCode::kInvalidArg);
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "a/b", p755, true}));
|
||||
|
||||
// remove a not empty directory
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, "a", AtFlags(), false}), MetaCode::kNotEmpty);
|
||||
// remove a not empty directory recursively
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "a", AtFlags(), true}));
|
||||
// stat removed directory
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, "a", AtFlags(AT_SYMLINK_NOFOLLOW)}), MetaCode::kNotFound);
|
||||
|
||||
auto result = co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto &inode = result->stat;
|
||||
CO_ASSERT_OK(co_await meta.hardLink({SUPER_USER, "file", "file-hardlink", AtFlags(AT_SYMLINK_NOFOLLOW)}));
|
||||
auto statResult = co_await meta.stat({SUPER_USER, "file-hardlink", AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(statResult);
|
||||
CO_ASSERT_EQ(inode.nlink, 1);
|
||||
CO_ASSERT_EQ(statResult->stat.nlink, 2);
|
||||
CO_ASSERT_EQ(inode.id, statResult->stat.id);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file", AtFlags(), false}));
|
||||
statResult = co_await meta.stat({SUPER_USER, "file-hardlink", AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(statResult);
|
||||
CO_ASSERT_EQ(statResult->stat.nlink, 1);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file-hardlink", AtFlags(), false}));
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, "file-hardlink", AtFlags(AT_SYMLINK_NOFOLLOW)}),
|
||||
MetaCode::kNotFound);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, RemoveRecursivePerm) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto ua = flat::UserInfo(flat::Uid(1), flat::Gid(1));
|
||||
auto ub = flat::UserInfo(flat::Uid(2), flat::Gid(2));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "shared", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "shared/ua/subdir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "shared/ua2/subdir", flat::Permission(0222), true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "shared/ua_ub/subdir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ub, "shared/ua_ub/subdir/dir", p700, false}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "shared/ua_ub2/subdir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ub, "shared/ua_ub2/subdir/dir", flat::Permission(0777 & S_ISVTX), false}));
|
||||
|
||||
FAULT_INJECTION_SET(10, 3);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({ua, "shared/ua2", AtFlags(), true}), MetaCode::kNoPermission);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({ub, "shared/ua", AtFlags(), true}), MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(co_await meta.remove({ua, "shared/ua", AtFlags(), true}));
|
||||
CO_ASSERT_ERROR(co_await meta.remove({ua, "shared/ua_ub", AtFlags(), true}), MetaCode::kNoPermission);
|
||||
CO_ASSERT_ERROR(co_await meta.remove({ua, "shared/ua_ub2", AtFlags(), true}), MetaCode::kNoPermission);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, RemoveRecursive) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// remove a non-empty directory recursively should remove subdirectory too.
|
||||
auto mkdirResult = co_await meta.mkdirs({SUPER_USER, "dir", p755, true});
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
auto dirId = mkdirResult->stat.id;
|
||||
// create a sub directory
|
||||
auto subDirResult = co_await meta.mkdirs({SUPER_USER, "dir/subdir", p755, true});
|
||||
CO_ASSERT_OK(subDirResult);
|
||||
auto subDirId = subDirResult->stat.id;
|
||||
// create a file under directory
|
||||
auto createResult = co_await meta.create({SUPER_USER, PathAt(subDirId, "file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(createResult);
|
||||
auto fileId = createResult->stat.id;
|
||||
|
||||
for (auto path : {"dir/.", "dir/..", "dir/subdir", "dir/subdir/file"}) {
|
||||
fmt::print("path {}\n", path);
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt(path), AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
}
|
||||
|
||||
// remove directory
|
||||
auto removeResult = co_await meta.remove({SUPER_USER, "dir", AtFlags(), true});
|
||||
CO_ASSERT_OK(removeResult);
|
||||
READ_ONLY_TRANSACTION({
|
||||
// all inodes present
|
||||
for (auto inodeId : {dirId, subDirId, fileId}) {
|
||||
CO_ASSERT_OK((co_await Inode::snapshotLoad(*txn, inodeId)).then(checkMetaFound<Inode>));
|
||||
}
|
||||
// dir entry should present
|
||||
for (auto [parent, name] : {std::pair(dirId, "subdir"), std::pair(subDirId, "file")}) {
|
||||
fmt::print("parent {}, name {}\n", parent, name);
|
||||
CO_ASSERT_OK((co_await DirEntry::snapshotLoad(*txn, parent, name)).then(checkMetaFound<DirEntry>));
|
||||
}
|
||||
});
|
||||
|
||||
// lookup at deleted directory
|
||||
for (auto [parent, name] : {std::pair{dirId, "subdir"}, {subDirId, "file"}}) {
|
||||
fmt::print("parent {}, name {}\n", parent, name);
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt(parent, name), AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
}
|
||||
for (auto path : {"dir/.", "dir/..", "dir/subdir", "dir/subdir/file"}) {
|
||||
fmt::print("path {}\n", path);
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, PathAt(path), AtFlags(AT_SYMLINK_FOLLOW)}), MetaCode::kNotFound);
|
||||
}
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, PathAt(dirId, "file2"), {}, O_EXCL, p644}), MetaCode::kNotFound);
|
||||
auto file2 = co_await meta.create({SUPER_USER, PathAt(subDirId, "file2"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(file2);
|
||||
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// all inodes removed
|
||||
for (auto inodeId : {dirId, subDirId, fileId, file2->stat.id}) {
|
||||
CO_ASSERT_ERROR((co_await Inode::snapshotLoad(*txn, inodeId)).then(checkMetaFound<Inode>), MetaCode::kNotFound);
|
||||
}
|
||||
});
|
||||
// after GC, directory has deleted, create under directory should get kNotFound
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, PathAt(dirId, "another-file"), {}, O_EXCL, p644}),
|
||||
MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.create({SUPER_USER, PathAt(subDirId, "another-file"), {}, O_EXCL, p644}),
|
||||
MetaCode::kNotFound);
|
||||
// lookup at deleted directory should get kNotFound
|
||||
for (auto pair : {std::pair{dirId, "."}, {dirId, ".."}, {dirId, "subdir"}, {subDirId, "file"}}) {
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, PathAt(pair.first, pair.second), AtFlags(AT_SYMLINK_FOLLOW)}),
|
||||
MetaCode::kNotFound);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, RemoveDirectoryByInode) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
FAULT_INJECTION_SET(10, 3);
|
||||
|
||||
// remove a non-empty directory recursively should remove subdirectory too.
|
||||
auto mkdirResult = co_await meta.mkdirs({SUPER_USER, "dir", p755, true});
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
auto dirId = mkdirResult->stat.id;
|
||||
// create a sub directory
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir/subdir", p755, true}));
|
||||
auto createResult = co_await meta.create({SUPER_USER, PathAt(dirId, "file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(createResult);
|
||||
auto fileId = createResult->stat.id;
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, fileId, AtFlags(), false}), MetaCode::kNotDirectory);
|
||||
// remove directory
|
||||
CO_ASSERT_ERROR(co_await meta.remove({SUPER_USER, dirId, AtFlags(), false}), MetaCode::kNotEmpty);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, dirId, AtFlags(), true}));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, Symlink) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt("file"), {}, O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, PathAt("symlink1"), "file"}));
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt("symlink1"), AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
CO_ASSERT_OK(
|
||||
co_await meta.hardLink({SUPER_USER, PathAt("symlink1"), PathAt("symlink2"), AtFlags(AT_SYMLINK_NOFOLLOW)}));
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt("symlink2"), AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, PathAt("symlink1"), AtFlags(AT_SYMLINK_NOFOLLOW), false}));
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt("symlink2"), AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
// remove file
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string path = std::to_string(i) + ".file";
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, 0, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto inodeId = result->stat.id;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = RemoveReq(SUPER_USER, Path(path), AtFlags(0), false);
|
||||
auto removeResult = co_await store.remove(req)->run(txn);
|
||||
CO_ASSERT_OK(removeResult);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
MetaTestHelper::getInodeKey(inodeId),
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(inodeId),
|
||||
}),
|
||||
false);
|
||||
}
|
||||
|
||||
// remove directory
|
||||
for (int i = 0; i < 1; i++) {
|
||||
std::string path = std::to_string(i) + ".directory";
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, path, p755, false});
|
||||
CO_ASSERT_OK(result);
|
||||
auto inodeId = result->stat.id;
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = RemoveReq(SUPER_USER, Path(path), AtFlags(0), false);
|
||||
auto removeResult = co_await store.remove(req)->run(txn);
|
||||
CO_ASSERT_OK(removeResult);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), path),
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(inodeId), // src Inode
|
||||
}),
|
||||
false);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, Idempotent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
config.mock_meta().set_idempotent_record_expire(5_s);
|
||||
config.mock_meta().set_idempotent_record_clean(1_s);
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto remove1 = RemoveReq({SUPER_USER, PathAt("file"), AtFlags(AT_SYMLINK_NOFOLLOW), false});
|
||||
auto remove2 = RemoveReq({SUPER_USER, PathAt("file"), AtFlags(AT_SYMLINK_NOFOLLOW), false});
|
||||
CO_ASSERT_ERROR(co_await meta.remove(remove1), MetaCode::kNotFound);
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt("file"), {}, O_EXCL, p644}));
|
||||
CO_ASSERT_ERROR(co_await meta.remove(remove1), MetaCode::kNotFound);
|
||||
CO_ASSERT_OK(co_await meta.remove(remove2));
|
||||
CO_ASSERT_OK(co_await meta.remove(remove2));
|
||||
CO_ASSERT_ERROR(co_await meta.remove(remove1), MetaCode::kNotFound);
|
||||
co_await folly::coro::sleep(2_s);
|
||||
CO_ASSERT_OK(co_await meta.remove(remove2));
|
||||
co_await folly::coro::sleep(5_s);
|
||||
CO_ASSERT_ERROR(co_await meta.remove(remove2), MetaCode::kNotFound);
|
||||
// remove check inode id
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt("file"), {}, O_EXCL, p644}));
|
||||
CO_ASSERT_ERROR(
|
||||
co_await meta.remove(
|
||||
{SUPER_USER, PathAt("file"), AtFlags(AT_SYMLINK_NOFOLLOW), false, false, InodeId(folly::Random::rand64())}),
|
||||
MetaCode::kNotFound);
|
||||
co_return;
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRemove, ConcurrentCreate) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(false);
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
// rmdir and concurrent create should conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
auto dirPath = std::to_string(i) + ".directory";
|
||||
auto childPath = "child";
|
||||
auto mkdir = co_await meta.mkdirs({SUPER_USER, dirPath, p755, true});
|
||||
CO_ASSERT_OK(mkdir);
|
||||
auto parentId = mkdir->stat.id;
|
||||
auto remove = [&](auto &txn) -> CoTask<void> {
|
||||
auto req = RemoveReq(SUPER_USER, Path(dirPath), AtFlags(0), false);
|
||||
auto removeResult = co_await store.remove(req)->run(txn);
|
||||
CO_ASSERT_OK(removeResult);
|
||||
};
|
||||
auto create = [&](auto &txn) -> CoTask<void> {
|
||||
if (i % 2 == 0) {
|
||||
auto req = MkdirsReq(SUPER_USER, PathAt(parentId, childPath), p755, false);
|
||||
auto mkdirResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
} else {
|
||||
auto req = CreateReq(SUPER_USER, PathAt(parentId, childPath), {}, O_RDONLY, p644);
|
||||
auto createResult = co_await BatchedOp::create(store, txn, req);
|
||||
CO_ASSERT_OK(createResult);
|
||||
}
|
||||
};
|
||||
if (i % 2 == 0) {
|
||||
CO_ASSERT_CONFLICT(remove, create);
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(create, remove);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
332
tests/meta/store/ops/TestRename.cc
Normal file
332
tests/meta/store/ops/TestRename.cc
Normal file
@@ -0,0 +1,332 @@
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/ops/BatchOperation.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestRename : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestRename, KVTypes);
|
||||
|
||||
TYPED_TEST(TestRename, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
FAULT_INJECTION_SET(10, 5);
|
||||
|
||||
// create a, rename a -> b, stat a -> InodeId::root(), stat b -> Inode
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "a", {}, O_RDONLY, p644}));
|
||||
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "a", "b"}));
|
||||
auto a = co_await meta.stat({SUPER_USER, "a", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_ERROR(a, MetaCode::kNotFound);
|
||||
auto b = co_await meta.stat({SUPER_USER, "b", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(b);
|
||||
|
||||
// create file c, d, rename c -> d, c will replace d
|
||||
InodeId cId;
|
||||
auto c = co_await meta.create({SUPER_USER, "c", {}, O_RDONLY, p644});
|
||||
auto d = co_await meta.create({SUPER_USER, "d", {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(c);
|
||||
CO_ASSERT_OK(d);
|
||||
cId = c->stat.id;
|
||||
|
||||
// check rename return inode
|
||||
auto renameResult = co_await meta.rename({SUPER_USER, "c", "d"});
|
||||
CO_ASSERT_OK(renameResult);
|
||||
CO_ASSERT_TRUE(renameResult->stat.has_value());
|
||||
CO_ASSERT_EQ(renameResult->stat->id, cId);
|
||||
auto statResult = co_await meta.stat({SUPER_USER, "d", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(statResult);
|
||||
CO_ASSERT_EQ(statResult->stat, renameResult->stat);
|
||||
|
||||
auto c1 = co_await meta.stat({SUPER_USER, "c", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_ERROR(c1, MetaCode::kNotFound);
|
||||
auto d1 = co_await meta.stat({SUPER_USER, "d", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(d1);
|
||||
CO_ASSERT_EQ(d1->stat.id, cId);
|
||||
|
||||
// create directory e, f/g, rename e -> f, should return not empty
|
||||
auto f = co_await meta.mkdirs({SUPER_USER, "e", p755, false});
|
||||
auto g = co_await meta.mkdirs({SUPER_USER, "f/g", p755, true});
|
||||
CO_ASSERT_OK(f);
|
||||
CO_ASSERT_OK(g);
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.rename({SUPER_USER, "e", "f"}), MetaCode::kNotEmpty);
|
||||
|
||||
// remove f/g
|
||||
FAULT_INJECTION_SET(0, 0);
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "f/g", AtFlags(), false}));
|
||||
|
||||
// rename e -> f again
|
||||
FAULT_INJECTION_SET(10, 5);
|
||||
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "e", "f"}));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRename, Directory) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir1", p777, false}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir2", p777, false}));
|
||||
auto stat1 = (co_await meta.stat({SUPER_USER, "dir1", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(stat1.asDirectory().name, "dir1");
|
||||
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "dir1", "dir2"}));
|
||||
auto stat2 = (co_await meta.stat({SUPER_USER, "dir2", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(stat2.asDirectory().name, "dir2");
|
||||
CO_ASSERT_EQ(stat2.id, stat1.id);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRename, Trash) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "trash", p777, false}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/a", {}, O_RDONLY, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "dir/b", {}, O_RDONLY, p644}));
|
||||
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "dir/a", "trash/trash_name", true}));
|
||||
CO_ASSERT_ERROR(co_await meta.rename({SUPER_USER, "dir/b", "trash/trash_name", true}), MetaCode::kExists);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRename, GC) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_gc_directory_delay(0_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(0_s);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// rename directory to trash, should be owner and rwx permission
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "trash", p777, true}));
|
||||
auto ua = flat::UserInfo(flat::Uid(1), flat::Gid(1));
|
||||
auto ub = flat::UserInfo(flat::Uid(2), flat::Gid(2));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "ua", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "ua/data-500", flat::Permission(0500), true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "ua/data-777", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "trash/ua", p777, true}));
|
||||
CO_ASSERT_ERROR(co_await meta.rename({ua, "ua/data-777", "trash/data-777"}), MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(co_await meta.rename({ua, "ua/data-777", "trash/data-777", true}));
|
||||
CO_ASSERT_ERROR(co_await meta.rename({ua, "ua/data-500", "trash/data-500", true}), MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(co_await meta.rename({ua, "trash/ua", "trash/ua-dest"}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "ua/dir_has_root", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "ua/dir_has_root/root", p700, true}));
|
||||
CO_ASSERT_ERROR(co_await meta.rename({ua, "ua/dir_has_root", "trash/dir_has_root", true}), MetaCode::kNoPermission);
|
||||
|
||||
// rename directory to another directory which is under GC, should fail
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir1/subdir", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "dir2", p777, false}));
|
||||
auto stat = (co_await meta.stat({SUPER_USER, "dir1", AtFlags()}))->stat;
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "dir1", AtFlags(), true}));
|
||||
CO_ASSERT_ERROR(co_await meta.rename({SUPER_USER, "dir2", PathAt(stat.id, "dir")}), MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.rename({SUPER_USER, "dir2", PathAt(InodeId::gcRoot(), "orphan")}),
|
||||
MetaCode::kNoPermission);
|
||||
|
||||
// rename replace a file, the file should be removed by GC.
|
||||
InodeId oldDstId;
|
||||
// create src and dst
|
||||
auto createSrc = co_await meta.create({SUPER_USER, "a", {}, O_RDONLY, p644});
|
||||
auto createDst = co_await meta.create({SUPER_USER, "b", {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(createSrc);
|
||||
CO_ASSERT_OK(createDst);
|
||||
oldDstId = createDst->stat.id;
|
||||
|
||||
// do rename to replace old dst
|
||||
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "a", "b"}));
|
||||
// wait GC tasks
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
// GC should remove old dst
|
||||
CO_ASSERT_INODE_NOT_EXISTS(oldDstId);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRename, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src = std::to_string(i) + ".src";
|
||||
std::string dst = std::to_string(i) + ".dst";
|
||||
InodeId srcId;
|
||||
// create src first
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, src, p755, false});
|
||||
CO_ASSERT_OK(result);
|
||||
srcId = result->stat.id;
|
||||
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = RenameReq(SUPER_USER, Path(src), Path(dst));
|
||||
auto removeResult = co_await store.rename(req)->run(txn);
|
||||
CO_ASSERT_OK(removeResult);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), src), // src dirEntry
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), dst), // dst dirEntry
|
||||
MetaTestHelper::getInodeKey(InodeId::root()), // dst parent Inode
|
||||
MetaTestHelper::getInodeKey(srcId), // src Inode
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), src), // src dirEntry
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), dst), // dst dirEntry
|
||||
MetaTestHelper::getInodeKey(srcId), // src Inode
|
||||
}),
|
||||
false);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestRename, Concurrent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
auto rename = [&](Path src, Path dst) -> std::function<CoTask<void>(IReadWriteTransaction &)> {
|
||||
return [=, &store](auto &txn) -> CoTask<void> { // rename src to dst
|
||||
auto req = RenameReq(SUPER_USER, Path(src), Path(dst));
|
||||
auto renameResult = co_await store.rename(req)->run(txn);
|
||||
CO_ASSERT_OK(renameResult);
|
||||
};
|
||||
};
|
||||
auto create =
|
||||
[&](InodeId parent, Path path, bool directory) -> std::function<CoTask<void>(IReadWriteTransaction &)> {
|
||||
EXPECT_FALSE(path.has_parent_path());
|
||||
return [=, &store](auto &txn) -> CoTask<void> { // create or mkdir at dst
|
||||
if (directory) {
|
||||
auto req = MkdirsReq(SUPER_USER, PathAt(parent, path), p755, true);
|
||||
auto mkdirResult = co_await store.mkdirs(req)->run(txn);
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
} else {
|
||||
auto req = CreateReq(SUPER_USER, PathAt(parent, path), {}, O_RDONLY, p644);
|
||||
CO_ASSERT_OK(co_await BatchedOp::create(store, txn, req));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// rename src -> dst and concurrent create dst should conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src = std::to_string(i) + ".src-1";
|
||||
std::string dst = std::to_string(i) + ".dst-1";
|
||||
// create src first
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, src, {}, O_RDONLY, p644}));
|
||||
if (i % 2 == 0) {
|
||||
CO_ASSERT_CONFLICT(rename(src, dst), create(InodeId::root(), dst, i % 2 == 0));
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(create(InodeId::root(), dst, i % 2 == 0), rename(src, dst));
|
||||
}
|
||||
}
|
||||
|
||||
// rename to same destination should conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src1 = std::to_string(i) + ".src-2.1";
|
||||
std::string src2 = std::to_string(i) + ".src-2.2";
|
||||
std::string dst = std::to_string(i) + ".dst-2";
|
||||
for (auto path : {src1, src2}) {
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644}));
|
||||
}
|
||||
auto rename1 = rename(src1, dst); // rename src1 to dst
|
||||
auto rename2 = rename(src2, dst); // rename src2 to dst
|
||||
if (i % 2 == 0) {
|
||||
CO_ASSERT_CONFLICT(rename1, rename2);
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(rename2, rename1);
|
||||
}
|
||||
}
|
||||
|
||||
// rename src to different dst should be conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src = std::to_string(i) + ".src-3";
|
||||
std::string dst1 = std::to_string(i) + ".dst-3.1";
|
||||
std::string dst2 = std::to_string(i) + ".dst-3.2";
|
||||
// create src1, src2 first
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, Path(src), {}, O_RDONLY, p644}));
|
||||
auto rename1 = rename(src, dst1); // rename src to dst1
|
||||
auto rename2 = rename(src, dst2); // rename src to dst2
|
||||
if (i % 2 == 0) {
|
||||
CO_ASSERT_CONFLICT(rename1, rename2);
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(rename2, rename1);
|
||||
}
|
||||
}
|
||||
|
||||
// rename different srcs to different dsts shouldn't be conflict
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src1 = std::to_string(i) + ".src-4.1";
|
||||
std::string src2 = std::to_string(i) + ".src-4.2";
|
||||
std::string dst1 = std::to_string(i) + ".dst-4.1";
|
||||
std::string dst2 = std::to_string(i) + ".dst-4.2";
|
||||
for (auto path : {src1, src2}) {
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644}));
|
||||
}
|
||||
auto rename1 = rename(src1, dst1); // rename src1 to dst1
|
||||
auto rename2 = rename(src2, dst2); // rename src2 to dst2
|
||||
CO_ASSERT_NO_CONFLICT(rename1, rename2);
|
||||
}
|
||||
|
||||
// rename replace a empty directory and create under directory should be conflict.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string src = std::to_string(i) + ".src-5";
|
||||
std::string dst = std::to_string(i) + ".dst-5";
|
||||
InodeId oldDstId;
|
||||
// create src and dst first
|
||||
auto result = co_await meta.mkdirs({SUPER_USER, src, p755, false});
|
||||
CO_ASSERT_OK(result);
|
||||
result = co_await meta.mkdirs({SUPER_USER, dst, p755, false});
|
||||
CO_ASSERT_OK(result);
|
||||
oldDstId = result->stat.id;
|
||||
if (i % 2 == 0) {
|
||||
CO_ASSERT_CONFLICT(rename(src, dst), create(oldDstId, "child", (i % 2 == 0)));
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(create(oldDstId, "child", (i % 2 == 0)), rename(src, dst));
|
||||
}
|
||||
}
|
||||
|
||||
// mkdir /a /b/d, rename /a -> /b/d/e and rename /b/d -> /a/c should conflict
|
||||
// mkdir /a, /b/d/e
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "a", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "b/d", p755, true}));
|
||||
if (folly::Random::oneIn(2)) {
|
||||
CO_ASSERT_CONFLICT(rename("/a", "/b/d/e"), rename("/b/d", "/a/c"));
|
||||
} else {
|
||||
CO_ASSERT_CONFLICT(rename("/b/d", "/a/c"), rename("/a", "/b/d/e"));
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
344
tests/meta/store/ops/TestResolve.cc
Normal file
344
tests/meta/store/ops/TestResolve.cc
Normal file
@@ -0,0 +1,344 @@
|
||||
#include <bits/types/FILE.h>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <iterator>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "meta/components/AclCache.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/PathResolve.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestResolve : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestResolve, KVTypes);
|
||||
|
||||
TYPED_TEST(TestResolve, ResolveComponent) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
AclCache aclCache(1 << 20);
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// when resolve with inexistent dentry, do not return error
|
||||
DirEntry parentEntry = DirEntry::newDirectory(MetaTestHelper::randomInodeId(),
|
||||
"not-exists-dir",
|
||||
MetaTestHelper::randomInodeId(),
|
||||
rootp777);
|
||||
auto resolveResult = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentEntry, "not-exists");
|
||||
CO_ASSERT_OK(resolveResult);
|
||||
|
||||
// parentId other than rootId will trigger inode load
|
||||
auto parentId = MetaTestHelper::randomInodeId();
|
||||
resolveResult = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentId, "not-exists");
|
||||
CO_ASSERT_ERROR(resolveResult, MetaCode::kNotFound);
|
||||
});
|
||||
|
||||
{ // if parent is not directory, should return kNotDirectory
|
||||
Inode parentInode = Inode::newFile(MetaTestHelper::randomInodeId(),
|
||||
rootp644,
|
||||
MetaTestHelper::randomLayout(),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto storeInodeResult = co_await parentInode.store(*txn);
|
||||
CO_ASSERT_OK(storeInodeResult);
|
||||
});
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto resolveResult =
|
||||
co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentInode.id, "not-exists");
|
||||
CO_ASSERT_ERROR(resolveResult, MetaCode::kNotDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// lookup not exist path, if user doesn't have permission, should return kNoPermission, otherwise return
|
||||
// kNotFound.
|
||||
Inode parentInode = Inode::newDirectory(MetaTestHelper::randomInodeId(),
|
||||
InodeId::root(),
|
||||
"parent",
|
||||
rootp700,
|
||||
Layout(),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
DirEntry parentEntry = DirEntry::newDirectory(InodeId::root(), "dir-name", parentInode.id, rootp700);
|
||||
CO_ASSERT_TRUE(parentInode.isDirectory());
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
// create a directory
|
||||
auto storeInodeResult = co_await parentInode.store(*txn);
|
||||
CO_ASSERT_OK(storeInodeResult);
|
||||
});
|
||||
READ_ONLY_TRANSACTION({
|
||||
// check permission
|
||||
flat::UserInfo otherUser(Uid(1), Gid(1), String());
|
||||
auto resolveResult =
|
||||
co_await PathResolveOp(*txn, aclCache, otherUser).pathComponent(parentInode.id, "not-exists");
|
||||
CO_ASSERT_ERROR(resolveResult, MetaCode::kNoPermission);
|
||||
|
||||
resolveResult = co_await PathResolveOp(*txn, aclCache, otherUser).pathComponent(parentEntry, "not-exists");
|
||||
CO_ASSERT_ERROR(resolveResult, MetaCode::kNoPermission);
|
||||
|
||||
// should return parent info if parent exists but dirEntry not exists
|
||||
// pass in parentId other than rootId will trigger inode load
|
||||
resolveResult = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentInode.id, "not-exists");
|
||||
CO_ASSERT_OK(resolveResult);
|
||||
CO_ASSERT_EQ(resolveResult->getParentId(), parentInode.id);
|
||||
CO_ASSERT_EQ(resolveResult->getParentAcl(), parentInode.acl);
|
||||
|
||||
resolveResult = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentEntry, "not-exists");
|
||||
CO_ASSERT_OK(resolveResult);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(resolveResult->parent));
|
||||
CO_ASSERT_EQ(resolveResult->getParentId(), parentInode.id);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// create a directory and a entry
|
||||
Inode parentInode = Inode::newDirectory(MetaTestHelper::randomInodeId(),
|
||||
InodeId::root(),
|
||||
"parent",
|
||||
rootp700,
|
||||
Layout(),
|
||||
UtcClock::now().castGranularity(1_s));
|
||||
DirEntry childEntry = DirEntry::newFile(parentInode.id, "file", MetaTestHelper::randomInodeId());
|
||||
CO_ASSERT_TRUE(parentInode.isDirectory());
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto storeInodeResult = co_await parentInode.store(*txn);
|
||||
auto storeEntryResult = co_await childEntry.store(*txn);
|
||||
CO_ASSERT_OK(storeInodeResult);
|
||||
CO_ASSERT_OK(storeEntryResult);
|
||||
});
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto resolveResult = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathComponent(parentInode.id, "file");
|
||||
CO_ASSERT_OK(resolveResult);
|
||||
CO_ASSERT_TRUE(std::holds_alternative<Inode>(resolveResult->parent));
|
||||
CO_ASSERT_EQ(std::get<Inode>(resolveResult->parent), parentInode);
|
||||
CO_ASSERT_TRUE(resolveResult->dirEntry.has_value());
|
||||
CO_ASSERT_EQ(resolveResult->dirEntry.value(), childEntry);
|
||||
});
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestResolve, pathRange) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
AclCache aclCache(1 << 20);
|
||||
READ_ONLY_TRANSACTION({
|
||||
// if parent doesn't exists, do not return error
|
||||
// if path is empty, return kNotFound
|
||||
auto parentId = MetaTestHelper::randomInodeId();
|
||||
auto path = PathAt(parentId, "/a/b/c");
|
||||
auto result1 = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathRange(path);
|
||||
CO_ASSERT_OK(result1);
|
||||
|
||||
path = PathAt(parentId, "a/b/c");
|
||||
auto result2 = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathRange(path);
|
||||
CO_ASSERT_ERROR(result2, MetaCode::kNotFound);
|
||||
});
|
||||
|
||||
// create some directory file and link
|
||||
auto ts = UtcClock::now().castGranularity(1_s);
|
||||
auto root = Inode::newDirectory(InodeId::root(), InodeId::root(), "/", rootp755, Layout(), ts);
|
||||
auto a =
|
||||
Inode::newDirectory(MetaTestHelper::randomInodeId(), InodeId::root(), "a", rootp700, Layout(), ts); // dir: /a
|
||||
auto aEntry = DirEntry::newDirectory(InodeId::root(), "a", a.id, rootp700);
|
||||
auto b = Inode::newDirectory(MetaTestHelper::randomInodeId(), a.id, "b", rootp700, Layout(), ts); // dir: /a/b
|
||||
auto bEntry = DirEntry::newDirectory(a.id, "b", b.id, rootp700);
|
||||
auto c = Inode::newDirectory(MetaTestHelper::randomInodeId(), b.id, "c", rootp700, Layout(), ts); // dir: /a/b/c
|
||||
auto cEntry = DirEntry::newDirectory(b.id, "c", c.id, rootp700);
|
||||
auto d = Inode::newFile(MetaTestHelper::randomInodeId(),
|
||||
rootp644,
|
||||
MetaTestHelper::randomLayout(),
|
||||
ts); // file: /a/b/c/d
|
||||
auto dEntry = DirEntry::newFile(c.id, "d", d.id);
|
||||
auto e = Inode::newSymlink(MetaTestHelper::randomInodeId(),
|
||||
"../c",
|
||||
rootUid,
|
||||
rootGid,
|
||||
ts); // symlink: /a/b/c/e -> '../c'
|
||||
auto eEntry = DirEntry::newSymlink(c.id, "e", e.id);
|
||||
auto f = Inode::newSymlink(MetaTestHelper::randomInodeId(), "d", rootUid, rootGid, ts); // symlink: /a/b/c/f -> 'd'
|
||||
auto fEntry = DirEntry::newSymlink(c.id, "f", f.id);
|
||||
auto g = Inode::newSymlink(MetaTestHelper::randomInodeId(),
|
||||
"g",
|
||||
rootUid,
|
||||
rootGid,
|
||||
ts); // symlink: /a/b/c/g -> 'g' (loop)
|
||||
auto gEntry = DirEntry::newSymlink(c.id, "g", g.id);
|
||||
std::vector<Inode> inodes = {root, a, b, c, d, e, f, g};
|
||||
std::vector<DirEntry> entries = {aEntry, bEntry, cEntry, dEntry, eEntry, fEntry, gEntry};
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
for (auto &inode : inodes) {
|
||||
CO_ASSERT_OK(co_await inode.store(*txn));
|
||||
}
|
||||
for (auto &entry : entries) {
|
||||
CO_ASSERT_OK(co_await entry.store(*txn));
|
||||
}
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve: /a/b/c/d -> d
|
||||
auto path = PathAt("/a/b/c/d");
|
||||
Path trace;
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER, &trace).pathRange(path);
|
||||
std::cout << "resolve " << *path.path << " -> " << trace << " " << trace.normalize() << std::endl;
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(result->missing.empty()) << result->missing;
|
||||
CO_ASSERT_EQ(result->getParentId(), c.id);
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), dEntry);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve: /a/b/c/e -> e
|
||||
auto path = Path("/a/b/c/e");
|
||||
Path trace;
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER, &trace).pathRange(path);
|
||||
std::cout << "resolve " << path << " -> " << trace << " " << trace.normalize() << std::endl;
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(result->missing.empty()) << result->missing;
|
||||
CO_ASSERT_EQ(result->getParentId(), c.id);
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), eEntry);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve: /a/b/c/e/d -> should get d
|
||||
auto path = PathAt("/a/b/c/e/d");
|
||||
Path trace;
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER, &trace).pathRange(path);
|
||||
CO_ASSERT_OK(result);
|
||||
std::cout << "resolve " << *path.path << " -> " << trace << " " << trace.normalize() << std::endl;
|
||||
CO_ASSERT_TRUE(result->missing.empty()) << result->missing;
|
||||
CO_ASSERT_EQ(result->getParentId(), c.id);
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), dEntry);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve: /a/b/c/f/xyz -> /a/b/c/f is symlink point to file, should get kNotDirectory
|
||||
auto path = PathAt("/a/b/c/f/xyz");
|
||||
Path trace;
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER, &trace).pathRange(path);
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotDirectory);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve: /a/b/c/g/xyz, /a/b/c/g is a symlink that cause loop, should get kTooManySymlinks
|
||||
auto path = PathAt("a/b/c/g/xyz"); // should still work
|
||||
Path trace;
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER, &trace).pathRange(path);
|
||||
CO_ASSERT_ERROR(result, MetaCode::kTooManySymlinks);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve /a/x/y/z -> stop at x
|
||||
auto path = PathAt("/a/x/y/z");
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER).pathRange(path);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->missing, "x/y/z");
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestResolve, path) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
AclCache aclCache(1 << 20);
|
||||
|
||||
// some path /a/b/c here
|
||||
auto ts = UtcClock::now().castGranularity(1_s);
|
||||
auto root = Inode::newDirectory(InodeId::root(), InodeId::root(), "/", rootp755, Layout(), ts);
|
||||
auto a =
|
||||
Inode::newDirectory(MetaTestHelper::randomInodeId(), InodeId::root(), "a", rootp700, Layout(), ts); // dir: /a
|
||||
auto aEntry = DirEntry::newDirectory(InodeId::root(), "a", a.id, rootp700);
|
||||
auto b = Inode::newDirectory(MetaTestHelper::randomInodeId(), a.id, "b", rootp700, Layout(), ts); // dir: /a/b
|
||||
auto bEntry = DirEntry::newDirectory(a.id, "b", b.id, rootp700);
|
||||
auto c = Inode::newDirectory(MetaTestHelper::randomInodeId(), b.id, "c", rootp700, Layout(), ts); // dir: /a/b/c
|
||||
auto cEntry = DirEntry::newDirectory(b.id, "c", c.id, rootp700);
|
||||
std::vector<Inode> inodes = {root, a, b, c};
|
||||
std::vector<DirEntry> entries = {aEntry, bEntry, cEntry};
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
for (auto &inode : inodes) {
|
||||
auto result = co_await inode.store(*txn);
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
for (auto &entry : entries) {
|
||||
auto result = co_await entry.store(*txn);
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
});
|
||||
|
||||
// todo: should add more test for follow symlink
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve /a/b/c
|
||||
auto result =
|
||||
co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/b/c"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(result->parent));
|
||||
CO_ASSERT_EQ(result->getParentId(), b.id);
|
||||
CO_ASSERT_TRUE(result->dirEntry.has_value());
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), cEntry);
|
||||
|
||||
// resolve /a/b/../b/c
|
||||
result =
|
||||
co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/b/../b/c"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(result->parent));
|
||||
CO_ASSERT_EQ(result->getParentId(), b.id);
|
||||
CO_ASSERT_TRUE(result->dirEntry.has_value());
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), cEntry);
|
||||
|
||||
// resolve /a/b/c/../c
|
||||
result =
|
||||
co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/b/../b/c"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(result->parent));
|
||||
CO_ASSERT_EQ(result->getParentId(), b.id);
|
||||
CO_ASSERT_TRUE(result->dirEntry.has_value());
|
||||
CO_ASSERT_EQ(result->dirEntry.value(), cEntry);
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve /a/d -> parent found but dirEntry not found
|
||||
auto result = co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/d"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(result->parent));
|
||||
CO_ASSERT_EQ(result->getParentId(), a.id);
|
||||
CO_ASSERT_FALSE(result->dirEntry.has_value());
|
||||
|
||||
// resolve /a/../a/d
|
||||
result = co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/d"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(!std::holds_alternative<Inode>(result->parent));
|
||||
CO_ASSERT_EQ(result->getParentId(), a.id);
|
||||
CO_ASSERT_FALSE(result->dirEntry.has_value());
|
||||
});
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
// resolve /a/d/e -> kNotFound
|
||||
auto result =
|
||||
co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/d/e"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
|
||||
// resolve /a/d/../d/e
|
||||
result = co_await PathResolveOp(*txn, aclCache, SUPER_USER).path(Path("/a/d/e"), AtFlags(AT_SYMLINK_NOFOLLOW));
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
});
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
145
tests/meta/store/ops/TestSetPermission.cc
Normal file
145
tests/meta/store/ops/TestSetPermission.cc
Normal file
@@ -0,0 +1,145 @@
|
||||
#include <algorithm>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/futures/ManualTimekeeper.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestSetPermission : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestSetPermission, KVTypes);
|
||||
|
||||
TYPED_TEST(TestSetPermission, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p777}));
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, "file", AtFlags(AT_SYMLINK_FOLLOW)}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, "file", AtFlags(), {}, {}, p700)));
|
||||
|
||||
flat::UserInfo ua(Uid(1), Gid(1), String());
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setPermission(ua, "file", AtFlags(), {}, {}, p755)),
|
||||
MetaCode::kNoPermission);
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, "file", AtFlags(), ua.uid, ua.gid, {})));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(ua, "file", AtFlags(), {}, {}, p700)));
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setPermission(ua, "file", AtFlags(), ua.uid, rootGid, {})),
|
||||
MetaCode::kNoPermission);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSetPermission, ByInodeId) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto file = (co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p777}))->stat;
|
||||
auto directory = (co_await meta.mkdirs({SUPER_USER, "directory", p777, false}))->stat;
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, file.id, AtFlags(), {}, {}, p700)));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, directory.id, AtFlags(), {}, {}, p700)));
|
||||
|
||||
auto fileStat = (co_await meta.stat({SUPER_USER, "file", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(fileStat.acl.perm, p700);
|
||||
auto dirStat = (co_await meta.stat({SUPER_USER, "directory", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(dirStat.acl.perm, p700);
|
||||
auto dirStat2 = (co_await meta.stat({SUPER_USER, "directory", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(dirStat2.acl.perm, p700);
|
||||
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
directory.asDirectory().name = "";
|
||||
CO_ASSERT_OK(co_await meta::server::Inode(directory).store(*txn));
|
||||
});
|
||||
|
||||
CO_ASSERT_OK(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, directory.id, AtFlags(), {}, {}, p755)));
|
||||
auto dirStat3 = (co_await meta.stat({SUPER_USER, "directory", AtFlags()}))->stat;
|
||||
CO_ASSERT_EQ(dirStat3.acl.perm, p755);
|
||||
CO_ASSERT_EQ(dirStat3.asDirectory().name, "directory");
|
||||
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setPermission(SUPER_USER, PathAt(directory.id, "."), AtFlags(), {}, {}, p700)));
|
||||
CO_ASSERT_EQ((co_await meta.stat({SUPER_USER, "directory", AtFlags()}))->stat.acl.perm, p700);
|
||||
READ_ONLY_TRANSACTION({
|
||||
CO_ASSERT_EQ((co_await DirEntry::snapshotLoad(*txn, InodeId::root(), "directory")).value()->dirAcl->perm, p700);
|
||||
CO_ASSERT_EQ((co_await DirEntry::snapshotLoad(*txn, directory.id, ".")).value(), std::nullopt);
|
||||
});
|
||||
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, PathAt(InodeId::root(), "directory"), AtFlags(), false}));
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, directory.id, AtFlags(), {}, {}, p700)),
|
||||
MetaCode::kNotFound);
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(
|
||||
SetAttrReq::setPermission(SUPER_USER, PathAt(directory.id, "."), AtFlags(), {}, {}, p700)),
|
||||
MetaCode::kNotFound);
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "directory", p777, false}));
|
||||
CO_ASSERT_ERROR(co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, directory.id, AtFlags(), {}, {}, p700)),
|
||||
MetaCode::kNotFound);
|
||||
|
||||
CO_ASSERT_ERROR(
|
||||
co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, InodeId::root(), AtFlags(), {}, {}, p700)),
|
||||
MetaCode::kNoPermission);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSetPermission, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
// create file, directory
|
||||
auto createResult = co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p777});
|
||||
CO_ASSERT_OK(createResult);
|
||||
auto fileId = createResult->stat.id;
|
||||
|
||||
auto mkdirResult = co_await meta.mkdirs({SUPER_USER, "directory", p777, false});
|
||||
CO_ASSERT_OK(mkdirResult);
|
||||
auto dirId = mkdirResult->stat.id;
|
||||
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = SetAttrReq::setPermission(SUPER_USER, "directory", AtFlags(0), {}, {}, p700);
|
||||
auto result = co_await store.setAttr(req)->run(txn);
|
||||
CO_ASSERT_OK(result);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), "directory"), // dir entry
|
||||
MetaTestHelper::getInodeKey(dirId), // inode
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getDirEntryKey(InodeId::root(), "directory"), // dir entry
|
||||
MetaTestHelper::getInodeKey(dirId), // inode
|
||||
}),
|
||||
true);
|
||||
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = SetAttrReq::setPermission(SUPER_USER, "file", AtFlags(0), {}, {}, p700);
|
||||
auto result = co_await store.setAttr(req)->run(txn);
|
||||
CO_ASSERT_OK(result);
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(fileId), // inode
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(fileId), // inode
|
||||
}),
|
||||
true);
|
||||
}());
|
||||
}
|
||||
} // namespace hf3fs::meta::server
|
||||
100
tests/meta/store/ops/TestStat.cc
Normal file
100
tests/meta/store/ops/TestStat.cc
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <fcntl.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestStat : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestStat, KVTypes);
|
||||
|
||||
TYPED_TEST(TestStat, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// create a
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "a/b/c", p755, true}));
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, "a", AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
auto a = result->stat;
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, "b", AtFlags(AT_SYMLINK_NOFOLLOW)}), MetaCode::kNotFound);
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, PathAt(a.id, "b"), AtFlags(AT_SYMLINK_NOFOLLOW)}));
|
||||
|
||||
result = co_await meta.stat({SUPER_USER, a.id, AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat, std::optional(a));
|
||||
|
||||
// create file
|
||||
auto cresult = co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p755});
|
||||
CO_ASSERT_OK(cresult);
|
||||
auto file = cresult->stat;
|
||||
|
||||
CO_ASSERT_ERROR(co_await meta.stat({SUPER_USER, PathAt(file.id, "."), AtFlags(AT_SYMLINK_NOFOLLOW)}),
|
||||
MetaCode::kNotDirectory);
|
||||
|
||||
result = co_await meta.stat({SUPER_USER, file.id, AtFlags(AT_SYMLINK_NOFOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat, std::optional(file));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestStat, FollowSymlink) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
// create a/b/c
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "a/b/c", p755, true}));
|
||||
// symlink a/d -> a/b
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "a/d", "/a/b"}));
|
||||
// a/d is symlink but not the last symlink
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, "a/d/c", AtFlags(AT_SYMLINK_NOFOLLOW)}));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestStat, FaultInjection) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
std::vector<std::string> paths;
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
std::string path = std::to_string(i);
|
||||
auto result = co_await meta.create({SUPER_USER, path, {}, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
paths.push_back("not_exists");
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
FAULT_INJECTION_SET(10, 3); // 10%, 3 faults
|
||||
auto path = paths[folly::Random::rand32(paths.size())];
|
||||
auto result = co_await meta.stat({SUPER_USER, path, AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
if (path == "not_exists") {
|
||||
CO_ASSERT_ERROR(result, MetaCode::kNotFound);
|
||||
} else {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
98
tests/meta/store/ops/TestSymlink.cc
Normal file
98
tests/meta/store/ops/TestSymlink.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <fcntl.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Path.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "meta/store/DirEntry.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/PathResolve.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestSymlink : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestSymlink, KVTypes);
|
||||
|
||||
TYPED_TEST(TestSymlink, RealPath) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
FAULT_INJECTION_SET(10, 5);
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "/a/b", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "/a/c", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p644}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "symlink1", "file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "symlink2", "/file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/symlink1", "../file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/symlink2", "/file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/symlink3", "../../file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/b/symlink1", "../../file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/b/symlink2", "../.././file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/b/symlink3", "../c/./../../../file"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/b/symlink4", "symlink3"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/c/symlink1", "/a/b/symlink4"}));
|
||||
|
||||
for (auto path : {"/symlink1",
|
||||
"symlink1",
|
||||
"symlink2",
|
||||
"/a/symlink1",
|
||||
"/a/symlink2",
|
||||
"/a/symlink3",
|
||||
"/a/b/symlink1",
|
||||
"/a/b/symlink2",
|
||||
"/a/b/symlink3",
|
||||
"/a/c/symlink1"}) {
|
||||
auto result = co_await meta.getRealPath({SUPER_USER, path, false});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->path, "/file") << path << " -> " << result->path;
|
||||
}
|
||||
|
||||
auto b = co_await meta.open({SUPER_USER, "/a/b", {}, O_RDONLY | O_DIRECTORY});
|
||||
CO_ASSERT_OK(b);
|
||||
for (auto path : {"symlink1",
|
||||
"symlink2",
|
||||
"./symlink3",
|
||||
"../b/symlink3",
|
||||
"../symlink1",
|
||||
"../symlink2",
|
||||
"../symlink3",
|
||||
"../../a/symlink3",
|
||||
"../../../a/b/symlink3"}) {
|
||||
auto result = co_await meta.getRealPath({SUPER_USER, PathAt(b->stat.id, path), false});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->path, "/file") << path << " -> " << result->path;
|
||||
}
|
||||
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/c/symlink2", "../b/../b"}));
|
||||
CO_ASSERT_OK(co_await meta.symlink({SUPER_USER, "/a/c/symlink3", "../b/../.."}));
|
||||
auto c = co_await meta.open({SUPER_USER, "/a/c", {}, O_RDONLY | O_DIRECTORY});
|
||||
CO_ASSERT_OK(c);
|
||||
|
||||
for (auto [path, rpath] : {std::make_pair("symlink2", "../b"),
|
||||
std::make_pair("symlink3", "../.."),
|
||||
{"symlink2/symlink1", "/file"},
|
||||
{"symlink2/../b", "../b"}}) {
|
||||
auto result = co_await meta.getRealPath({SUPER_USER, PathAt(c->stat.id, path), false});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->path, rpath) << path << " -> " << result->path << " != " << rpath;
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
228
tests/meta/store/ops/TestSync.cc
Normal file
228
tests/meta/store/ops/TestSync.cc
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/storage/Common.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "meta/store/ops/BatchOperation.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestSync : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestSync, KVTypes);
|
||||
|
||||
TYPED_TEST(TestSync, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
bool dynStripe = i % 2;
|
||||
auto create = co_await meta.create(
|
||||
{SUPER_USER, PathAt(fmt::format("test-file-{}", i)), {}, O_EXCL, p644, std::nullopt, dynStripe});
|
||||
CO_ASSERT_OK(create);
|
||||
auto inode = create->stat;
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
CO_ASSERT_EQ(inode.asFile().dynStripe != 0, dynStripe);
|
||||
|
||||
// sync directory
|
||||
CO_ASSERT_ERROR(co_await meta.sync({SUPER_USER, InodeId::root(), true, std::nullopt, std::nullopt}),
|
||||
MetaCode::kNotFile);
|
||||
|
||||
// sync empty file, length should be 0
|
||||
auto result = co_await meta.sync({SUPER_USER, inode.id, true, {}, {}});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, 0);
|
||||
|
||||
// write at random offset, sync and check new length
|
||||
#ifdef GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||
for (size_t i = 0; i < 50; i++) {
|
||||
auto offset = folly::Random::rand64(10_GB);
|
||||
auto length = folly::Random::rand64(2_MB);
|
||||
#else
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto offset = folly::Random::rand64(100_MB);
|
||||
auto length = folly::Random::rand64(1_MB);
|
||||
#endif
|
||||
auto expectedLength = std::max(inode.asFile().length, offset + length);
|
||||
co_await randomWrite(meta, storage, inode, offset, length);
|
||||
|
||||
auto stat = co_await meta.stat({SUPER_USER, inode.id, AtFlags()});
|
||||
CO_ASSERT_OK(stat);
|
||||
|
||||
auto newLength = co_await fileHelper.queryLength(SUPER_USER, stat->stat, nullptr);
|
||||
CO_ASSERT_OK(newLength);
|
||||
CO_ASSERT_EQ(expectedLength, *newLength);
|
||||
inode.asFile().length = expectedLength;
|
||||
|
||||
{
|
||||
// sync file
|
||||
auto result = co_await meta.sync({SUPER_USER, inode.id, true, {}, {}});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, *newLength);
|
||||
}
|
||||
|
||||
// stat file
|
||||
auto result = co_await meta.stat({SUPER_USER, inode.id, AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, *newLength);
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSync, Concurrent) {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
// start multiple workers to truncate this file
|
||||
auto worker = [&](Inode &inode) -> CoTask<void> {
|
||||
#ifdef GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||
for (size_t i = 0; i < folly::Random::rand32(100); i++) {
|
||||
auto offset = folly::Random::rand64(500_MB);
|
||||
auto length = folly::Random::rand64(2_MB);
|
||||
#else
|
||||
for (size_t i = 0; i < folly::Random::rand32(10); i++) {
|
||||
auto offset = folly::Random::rand64(500_MB);
|
||||
auto length = folly::Random::rand64(1_MB);
|
||||
#endif
|
||||
CO_ASSERT_NE(inode.id, InodeId());
|
||||
CO_ASSERT_TRUE(inode.isFile());
|
||||
auto newLength = std::max(inode.asFile().length, offset + length);
|
||||
if (folly::Random::oneIn(50)) {
|
||||
co_await truncate(meta, storage, inode, newLength);
|
||||
} else {
|
||||
co_await randomWrite(meta, storage, inode, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
CO_ASSERT_OK(co_await meta.sync({SUPER_USER, inode.id, true, {}, {}}));
|
||||
};
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
Inode inode = create->stat;
|
||||
CO_ASSERT_TRUE(inode.isFile());
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
for (size_t i = 0; i < 64; i++) {
|
||||
futures.push_back(folly::coro::co_invoke(worker, inode).scheduleOn(&exec).start());
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
auto newLength = co_await fileHelper.queryLength(SUPER_USER, inode, nullptr);
|
||||
CO_ASSERT_OK(newLength);
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, inode.id, AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(result->stat.isFile());
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, *newLength);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSync, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.set_num_meta(1);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
InodeId inodeId = create->stat.id;
|
||||
|
||||
BatchedOp::Waiter<SyncReq, SyncRsp> waiter(
|
||||
SyncReq(SUPER_USER, inodeId, true, {}, {}, false, VersionedLength{10, 0}));
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
BatchedOp op(store, inodeId);
|
||||
op.add(waiter);
|
||||
CO_ASSERT_OK(co_await op.run(txn));
|
||||
},
|
||||
(std::vector<String>{MetaTestHelper::getInodeKey(inodeId)}),
|
||||
(std::vector<String>{MetaTestHelper::getInodeKey(inodeId)}),
|
||||
false);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSync, hint) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
InodeId inodeId = create->stat.id;
|
||||
|
||||
// invalid hint, DFATAL
|
||||
// CO_ASSERT_ERROR(co_await meta.sync({SUPER_USER, inodeId, true, std::nullopt, std::nullopt, false,
|
||||
// VersionedLength{100, 10}}), MetaCode::kFoundBug);
|
||||
|
||||
// sync truncate
|
||||
auto r1 =
|
||||
co_await meta.sync({SUPER_USER, inodeId, true, std::nullopt, std::nullopt, true, VersionedLength{100, 0}});
|
||||
CO_ASSERT_OK(r1);
|
||||
CO_ASSERT_EQ(r1->stat.asFile().getVersionedLength(), (VersionedLength{0, 1}));
|
||||
|
||||
// sync with hint
|
||||
auto r2 =
|
||||
co_await meta.sync({SUPER_USER, inodeId, true, std::nullopt, std::nullopt, false, VersionedLength{1024, 1}});
|
||||
CO_ASSERT_OK(r2);
|
||||
CO_ASSERT_EQ(r2->stat.asFile().getVersionedLength(), (VersionedLength{1024, 1}));
|
||||
|
||||
// sync with outdate hint
|
||||
auto r3 =
|
||||
co_await meta.sync({SUPER_USER, inodeId, true, std::nullopt, std::nullopt, false, VersionedLength{6000, 0}});
|
||||
CO_ASSERT_OK(r3);
|
||||
CO_ASSERT_EQ(r3->stat.asFile().getVersionedLength(), (VersionedLength{0, 2}));
|
||||
|
||||
// sync with hint
|
||||
auto r4 =
|
||||
co_await meta.sync({SUPER_USER, inodeId, true, std::nullopt, std::nullopt, false, VersionedLength{6000, 2}});
|
||||
CO_ASSERT_OK(r4);
|
||||
CO_ASSERT_EQ(r4->stat.asFile().getVersionedLength(), (VersionedLength{6000, 2}));
|
||||
|
||||
// close
|
||||
auto r5 =
|
||||
co_await meta.close({SUPER_USER, inodeId, MetaTestHelper::randomSession(), true, std::nullopt, std::nullopt});
|
||||
CO_ASSERT_OK(r5);
|
||||
CO_ASSERT_EQ(r5->stat.asFile().getVersionedLength(), (VersionedLength{0, 3}));
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
160
tests/meta/store/ops/TestTruncate.cc
Normal file
160
tests/meta/store/ops/TestTruncate.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
#include <atomic>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Task.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/FileHelper.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
namespace {
|
||||
|
||||
template <typename KV>
|
||||
class TestTruncate : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestTruncate, KVTypes);
|
||||
|
||||
TYPED_TEST(TestTruncate, Basic) {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
auto truncateAndStat = [&](Inode &inode, uint64_t targetLength) -> CoTask<void> {
|
||||
co_await truncate(meta, storage, inode, targetLength);
|
||||
|
||||
// do stat, should get new length.
|
||||
auto result = co_await meta.stat({SUPER_USER, PathAt("test-file"), AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->stat.asFile().length, targetLength);
|
||||
|
||||
// query from file helper, should get same length.
|
||||
auto newLength = co_await fileHelper.queryLength(SUPER_USER, inode, nullptr);
|
||||
CO_ASSERT_OK(newLength);
|
||||
CO_ASSERT_EQ(*newLength, targetLength);
|
||||
};
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto create =
|
||||
co_await meta.create({SUPER_USER, "test-file", {}, O_EXCL, p644, Layout::newEmpty(ChainTableId(1), 1_KB, 128)});
|
||||
CO_ASSERT_OK(create);
|
||||
Inode inode(create->stat);
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
|
||||
// file length should be zero
|
||||
auto newLength = co_await fileHelper.queryLength(SUPER_USER, inode, nullptr);
|
||||
CO_ASSERT_OK(newLength);
|
||||
CO_ASSERT_EQ(*newLength, 0);
|
||||
|
||||
// truncate file up
|
||||
co_await truncateAndStat(inode, 2_MB + 13);
|
||||
co_await truncateAndStat(inode, 5_MB - 1);
|
||||
|
||||
// truncate file down
|
||||
co_await truncateAndStat(inode, 2_MB - 1023);
|
||||
co_await truncateAndStat(inode, 512_KB - 1);
|
||||
co_await truncateAndStat(inode, 0);
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
co_await truncateAndStat(inode, folly::Random::rand64(2_MB));
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTruncate, Deperated) {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto create =
|
||||
co_await meta.create({SUPER_USER, "test-file", {}, O_EXCL, p644, Layout::newEmpty(ChainTableId(1), 1_KB, 1)});
|
||||
CO_ASSERT_OK(create);
|
||||
Inode inode(create->stat);
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
CO_ASSERT_ERROR(co_await meta.truncate({SUPER_USER, inode.id, 512, 1}), StatusCode::kNotImplemented);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTruncate, Concurrent) {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// start multiple workers to truncate this file
|
||||
auto create = co_await meta.create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
CO_ASSERT_OK(create);
|
||||
auto inode = create->stat;
|
||||
CO_ASSERT_EQ(inode.asFile().length, 0);
|
||||
|
||||
auto worker = [&]() -> CoTask<void> {
|
||||
if (folly::Random::oneIn(4)) {
|
||||
co_await truncate(meta, storage, inode, 0);
|
||||
} else {
|
||||
co_await truncate(meta, storage, inode, folly::Random::rand64(10_MB, 20_MB));
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
for (size_t i = 0; i < 64; i++) {
|
||||
futures.push_back(folly::coro::co_invoke(worker).scheduleOn(&exec).start());
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, PathAt("test-file"), AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
inode = result->stat;
|
||||
auto length = inode.asFile().length;
|
||||
CO_ASSERT_TRUE(length < 20_MB) << length;
|
||||
|
||||
auto newLength = co_await fileHelper.queryLength(SUPER_USER, inode, nullptr);
|
||||
CO_ASSERT_OK(newLength);
|
||||
std::cout << fmt::format("{}", inode.asFile().getVersionedLength()) << " " << *newLength << std::endl;
|
||||
CO_ASSERT_EQ(length, *newLength);
|
||||
}());
|
||||
}
|
||||
|
||||
// TYPED_TEST(TestTruncate, ConflictSet) {
|
||||
// folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// auto cluster = this->createMockCluster();
|
||||
// auto &store = cluster.meta().getStore();
|
||||
|
||||
// auto create = co_await cluster.meta().getOperator().create({SUPER_USER, PathAt("test-file"), {}, O_EXCL, p644});
|
||||
// CO_ASSERT_OK(create);
|
||||
// InodeId inodeId = create->stat.id;
|
||||
|
||||
// CHECK_CONFLICT_SET(
|
||||
// [&](auto &txn) -> CoTask<void> {
|
||||
// auto req = TruncateReq(SUPER_USER, inodeId, 1_MB, 32);
|
||||
// CO_ASSERT_OK(co_await store.truncate(req)->run(txn));
|
||||
// },
|
||||
// (std::vector<String>{MetaTestHelper::getInodeKey(inodeId)}),
|
||||
// (std::vector<String>{MetaTestHelper::getInodeKey(inodeId)}),
|
||||
// false);
|
||||
// }());
|
||||
// }
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server
|
||||
118
tests/meta/store/ops/TestUtimes.cc
Normal file
118
tests/meta/store/ops/TestUtimes.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/futures/ManualTimekeeper.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/StatusCode.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "fbs/meta/Service.h"
|
||||
#include "meta/store/MetaStore.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
template <typename KV>
|
||||
class TestUtimes : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestUtimes, KVTypes);
|
||||
|
||||
TYPED_TEST(TestUtimes, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p777}));
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, "file", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
auto &inode = result->stat;
|
||||
|
||||
auto test =
|
||||
[&](const flat::UserInfo &user, PathAt path, UtcTime atime, UtcTime mtime, uint64_t error = 0) -> CoTask<void> {
|
||||
auto gran = cluster.config().mock_meta().time_granularity();
|
||||
atime = (atime == SETATTR_TIME_NOW) ? SETATTR_TIME_NOW : atime.castGranularity(gran);
|
||||
mtime = (atime == SETATTR_TIME_NOW) ? SETATTR_TIME_NOW : mtime.castGranularity(gran);
|
||||
auto before = UtcClock::now().castGranularity(gran);
|
||||
auto result = co_await meta.setAttr(SetAttrReq::utimes(user, path, AtFlags(0), atime, mtime));
|
||||
if (error) {
|
||||
CO_ASSERT_ERROR(result, error);
|
||||
co_return;
|
||||
} else {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
|
||||
auto sresult = co_await meta.stat({SUPER_USER, "file", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(sresult);
|
||||
auto &inode = sresult->stat;
|
||||
if (atime != SETATTR_TIME_NOW) {
|
||||
CO_ASSERT_EQ(inode.atime, atime) << fmt::format("{} {}", inode.atime, atime);
|
||||
} else {
|
||||
CO_ASSERT_GE(inode.atime, before);
|
||||
}
|
||||
if (mtime != SETATTR_TIME_NOW) {
|
||||
CO_ASSERT_EQ(inode.mtime, mtime) << fmt::format("{} {}", inode.mtime, mtime);
|
||||
} else {
|
||||
CO_ASSERT_GE(inode.mtime, before);
|
||||
}
|
||||
};
|
||||
|
||||
co_await test(SUPER_USER, "file", UtcClock::now(), UtcClock::now());
|
||||
co_await test(SUPER_USER, "file", SETATTR_TIME_NOW, SETATTR_TIME_NOW);
|
||||
co_await test(SUPER_USER, inode.id, UtcClock::now(), UtcClock::now());
|
||||
co_await test(SUPER_USER, inode.id, SETATTR_TIME_NOW, SETATTR_TIME_NOW);
|
||||
|
||||
flat::UserInfo ua(Uid(1), Gid(1), String());
|
||||
co_await test(ua, "file", SETATTR_TIME_NOW, SETATTR_TIME_NOW);
|
||||
co_await test(ua, "file", UtcClock::now(), UtcClock::now(), MetaCode::kNoPermission);
|
||||
co_await test(ua, inode.id, SETATTR_TIME_NOW, SETATTR_TIME_NOW);
|
||||
co_await test(ua, inode.id, UtcClock::now(), UtcClock::now(), MetaCode::kNoPermission);
|
||||
|
||||
CO_ASSERT_OK(
|
||||
co_await meta.setAttr(SetAttrReq::setPermission(SUPER_USER, "file", AtFlags(0), {}, {}, std::optional(p700))));
|
||||
|
||||
co_await test(ua, "file", SETATTR_TIME_NOW, SETATTR_TIME_NOW, MetaCode::kNoPermission);
|
||||
co_await test(ua, "file", UtcClock::now(), UtcClock::now(), MetaCode::kNoPermission);
|
||||
co_await test(ua, inode.id, SETATTR_TIME_NOW, SETATTR_TIME_NOW, MetaCode::kNoPermission);
|
||||
co_await test(ua, inode.id, UtcClock::now(), UtcClock::now(), MetaCode::kNoPermission);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestUtimes, ConflictSet) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &store = cluster.meta().getStore();
|
||||
|
||||
// create file, directory and symlink
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "file", {}, O_RDONLY, p777}));
|
||||
|
||||
auto result = co_await meta.stat({SUPER_USER, "file", AtFlags(AT_SYMLINK_FOLLOW)});
|
||||
CO_ASSERT_OK(result);
|
||||
auto fileId = result->stat.id;
|
||||
|
||||
CHECK_CONFLICT_SET(
|
||||
[&](auto &txn) -> CoTask<void> {
|
||||
auto req = SetAttrReq::utimes(SUPER_USER,
|
||||
"file",
|
||||
AtFlags(0),
|
||||
UtcTime::fromMicroseconds(folly::Random::rand32()),
|
||||
UtcTime::fromMicroseconds(folly::Random::rand32()));
|
||||
CO_ASSERT_OK(co_await store.setAttr(req)->run(txn));
|
||||
},
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(fileId), // inode
|
||||
}),
|
||||
(std::vector<String>{
|
||||
MetaTestHelper::getInodeKey(fileId), // inode
|
||||
}),
|
||||
true);
|
||||
}());
|
||||
}
|
||||
} // namespace hf3fs::meta::server
|
||||
Reference in New Issue
Block a user