#include #include "MgmtdTestHelper.h" #include "common/kv/KeyPrefix.h" #include "common/kv/mem/MemKVEngine.h" #include "mgmtd/ops/Include.h" #include "mgmtd/service/MgmtdOperator.h" #include "tests/GtestHelpers.h" namespace hf3fs::mgmtd::testing { class MgmtdOperatorTest : public ::testing::Test { protected: MgmtdOperatorTest() { flat::AppInfo info; info.nodeId = flat::NodeId(10000); info.clusterId = "clusterId"; info.hostname = "hostname0"; info.pid = 100; flat::ServiceGroupInfo sgi0; sgi0.services.insert("MgmtdService"); sgi0.endpoints.push_back(net::Address::fromString("127.0.0.1:9999")); info.serviceGroups.push_back(std::move(sgi0)); defaultConfig_.set_lease_length(1_ms); defaultConfig_.set_extend_lease_interval(0_s); defaultConfig_.set_suspicious_lease_interval(100_us); defaultConfig_.set_heartbeat_timestamp_valid_window(1_ms); defaultConfig_.set_allow_heartbeat_from_unregistered(false); env_ = std::make_shared(); env_->setAppInfo(info); env_->setKvEngine(std::make_shared()); env_->setUtcTimeGenerator(generator_); env_->setBackgroundExecutor(&executor_); } flat::HeartbeatInfo from(flat::NodeId id) { flat::HeartbeatInfo info; info.app.nodeId = id; return info; } flat::HeartbeatInfo from(flat::NodeId id, flat::HeartbeatInfo info) { info.app.nodeId = id; return info; } CoTryTask setPrimary(MgmtdOperator &mgmtd, flat::NodeId primaryId, UtcTime now) { MgmtdStore store; flat::PersistentNodeInfo info; info.nodeId = primaryId; MgmtdTestHelper helper(mgmtd); now_ = now; auto txn = helper.getEngine().createReadWriteTransaction(); auto result = co_await store.extendLease(*txn, info, std::chrono::microseconds(1000), now); CO_RETURN_ON_ERROR(result); co_return co_await txn->commit(); } CoTryTask getPrimaryMgmtd(MgmtdOperator &mgmtd, const String &clusterId) { co_return co_await mgmtd.getPrimaryMgmtd(mgmtd::GetPrimaryMgmtdReq::create(clusterId), {}); } CoTryTask getRoutingInfo(MgmtdOperator &mgmtd, const String &clusterId, flat::RoutingInfoVersion version) { co_return co_await mgmtd.getRoutingInfo(mgmtd::GetRoutingInfoReq::create(clusterId, version), {}); } CoTryTask heartbeat(MgmtdOperator &mgmtd, const String &clusterId, const flat::HeartbeatInfo &hb, UtcTime timestamp) { co_return co_await mgmtd.heartbeat(mgmtd::HeartbeatReq::create(clusterId, hb, timestamp), {}); } CoTryTask enableNode(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeId nodeId) { co_return co_await mgmtd.enableNode(mgmtd::EnableNodeReq::create(clusterId, nodeId), {}); } CoTryTask disableNode(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeId nodeId) { co_return co_await mgmtd.disableNode(mgmtd::DisableNodeReq::create(clusterId, nodeId), {}); } CoTryTask registerNode(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeId nodeId, flat::NodeType type) { co_return co_await mgmtd.registerNode(mgmtd::RegisterNodeReq::create(clusterId, nodeId, type), {}); } CoTryTask setConfig(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeType nodeType, const String &content) { co_return co_await mgmtd.setConfig(mgmtd::SetConfigReq::create(clusterId, nodeType, content), {}); } CoTryTask getConfig(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeType nodeType, flat::ConfigVersion version) { co_return co_await mgmtd.getConfig(mgmtd::GetConfigReq::create(clusterId, nodeType, version), {}); } CoTryTask setChainTable(MgmtdOperator &mgmtd, const String &clusterId, flat::ChainTableId tableId, const std::vector &chains) { co_return co_await mgmtd.setChainTable(mgmtd::SetChainTableReq::create(clusterId, tableId, chains), {}); } CoTryTask extendClientSession(MgmtdOperator &mgmtd, auto &&...args) { co_return co_await mgmtd.extendClientSession( mgmtd::ExtendClientSessionReq::create(std::forward(args)...), {}); } CoTryTask listClientSessions(MgmtdOperator &mgmtd, const String &clusterId) { co_return co_await mgmtd.listClientSessions(mgmtd::ListClientSessionsReq::create(clusterId), {}); } CoTryTask setNodeTags(MgmtdOperator &mgmtd, const String &clusterId, flat::NodeId nodeId, std::vector tags, flat::SetTagMode mode) { co_return co_await mgmtd.setNodeTags(mgmtd::SetNodeTagsReq::create(clusterId, nodeId, std::move(tags), mode), {}); } UtcTime now_ = UtcTime::fromMicroseconds(10000); std::function generator_ = [this] { return now_; }; MgmtdConfig defaultConfig_; std::shared_ptr env_; CPUExecutorGroup executor_{4, "TEST"}; }; TEST_F(MgmtdOperatorTest, testGetPrimaryMgmtd) { MgmtdOperator mgmtd(env_, defaultConfig_); folly::coro::blockingWait([&]() -> CoTask { // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, getPrimaryMgmtd(mgmtd, "wrong")); // no lease holder { auto ret = co_await getPrimaryMgmtd(mgmtd, "clusterId"); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(!ret->primary.has_value()); } // node1 got lease CO_AWAIT_ASSERT_OK(setPrimary(mgmtd, flat::NodeId(1), UtcTime::fromMicroseconds(10000))); // node0 doesn't know node1's lease, load from store { auto ret = co_await getPrimaryMgmtd(mgmtd, "clusterId"); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(ret->primary.has_value()); CO_ASSERT_EQ(ret->primary->nodeId, flat::NodeId(1)); } // node0 has known node1's lease, directly return { MgmtdTestHelper helper(mgmtd); co_await helper.extendLease(); // hack: erase lease from engine to ensure the result come from cached lease auto txn = helper.getEngine().createReadWriteTransaction(); auto key = fmt::format("{}MgmtdLease", kv::toStr(kv::KeyPrefix::Single)); CO_AWAIT_ASSERT_OK(txn->clear(key)); CO_AWAIT_ASSERT_OK(txn->commit()); auto ret = co_await getPrimaryMgmtd(mgmtd, "clusterId"); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(ret->primary.has_value()); CO_ASSERT_EQ(ret->primary->nodeId, flat::NodeId(1)); } // node1's lease isn't trustable { // node2 got lease CO_AWAIT_ASSERT_OK(setPrimary(mgmtd, flat::NodeId(2), UtcTime::fromMicroseconds(11001))); now_ = UtcTime::fromMicroseconds(10950); auto ret = co_await getPrimaryMgmtd(mgmtd, "clusterId"); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(ret->primary.has_value()); CO_ASSERT_EQ(ret->primary->nodeId, flat::NodeId(2)); } }()); } TEST_F(MgmtdOperatorTest, testRegisterNode) { MgmtdOperator mgmtd(env_, defaultConfig_); folly::coro::blockingWait([&]() -> CoTask { // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, registerNode(mgmtd, "wrong", flat::NodeId(1), flat::NodeType::MGMTD)); // not primary: no primary right now CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); // not primary: not self CO_AWAIT_ASSERT_OK(setPrimary(mgmtd, flat::NodeId(1), UtcTime::fromMicroseconds(10000))); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, registerNode(mgmtd, "clusterId", flat::NodeId(2), flat::NodeType::MGMTD)); // not primary: cached lease isn't trustable { now_ = UtcTime::fromMicroseconds(10950); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, registerNode(mgmtd, "clusterId", flat::NodeId(2), flat::NodeType::MGMTD)); } // succeed: self holds un-suspicious lease { now_ = UtcTime::fromMicroseconds(12000); co_await MgmtdTestHelper(mgmtd).extendLease(); CO_AWAIT_ASSERT_OK(registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); auto ret = co_await getRoutingInfo(mgmtd, "clusterId", flat::RoutingInfoVersion(0)); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(ret->info.has_value()); CO_ASSERT_TRUE(ret->info->nodes.contains(flat::NodeId(1))); const auto &node = ret->info->nodes[flat::NodeId(1)]; CO_ASSERT_EQ(node.app.nodeId, flat::NodeId(1)); CO_ASSERT_EQ(node.type, flat::NodeType::MGMTD); CO_ASSERT_EQ(node.status, flat::NodeStatus::HEARTBEAT_CONNECTING); } // duplicated CO_AWAIT_ASSERT_ERROR(MgmtdCode::kRegisterFail, registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); // not primary: self lease is suspicious { now_ = UtcTime::fromMicroseconds(12950); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, registerNode(mgmtd, "clusterId", flat::NodeId(2), flat::NodeType::MGMTD)); } }()); } TEST_F(MgmtdOperatorTest, testGetRoutingInfo) { MgmtdOperator mgmtd(env_, defaultConfig_); folly::coro::blockingWait([&]() -> CoTask { // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, getRoutingInfo(mgmtd, "wrong", flat::RoutingInfoVersion(0))); // not primary: no primary right now CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, getRoutingInfo(mgmtd, "clusterId", flat::RoutingInfoVersion(1))); // init co_await MgmtdTestHelper(mgmtd).extendLease(); { auto ret = co_await getRoutingInfo(mgmtd, "clusterId", flat::RoutingInfoVersion(0)); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(ret->info.has_value()); CO_ASSERT_EQ(ret->info->routingInfoVersion, flat::RoutingInfoVersion(1)); CO_ASSERT_EQ(ret->info->nodes.size(), 1); CO_ASSERT_TRUE(ret->info->nodes.contains(flat::NodeId(10000))); // self const auto &node = ret->info->nodes[flat::NodeId(10000)]; CO_ASSERT_EQ(node.app.nodeId, flat::NodeId(10000)); CO_ASSERT_EQ(node.type, flat::NodeType::MGMTD); CO_ASSERT_EQ(node.status, flat::NodeStatus::PRIMARY_MGMTD); } // return nullopt when version is up-to-date { auto ret = co_await getRoutingInfo(mgmtd, "clusterId", flat::RoutingInfoVersion(1)); CO_ASSERT_OK(ret); CO_ASSERT_TRUE(!ret->info.has_value()); } // return error when version is newer than server's CO_AWAIT_ASSERT_ERROR(MgmtdCode::kInvalidRoutingInfoVersion, getRoutingInfo(mgmtd, "clusterId", flat::RoutingInfoVersion(2))); }()); } TEST_F(MgmtdOperatorTest, testHeartbeat) { MgmtdOperator mgmtd(env_, defaultConfig_); folly::coro::blockingWait([&]() -> CoTask { // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, heartbeat(mgmtd, "wrong", from(flat::NodeId{1}), now_)); flat::HeartbeatInfo info1; { info1.app.nodeId = flat::NodeId(1); info1.app.hostname = "hostname1"; info1.app.pid = 101; flat::ServiceGroupInfo sgi0; sgi0.services.emplace("MgmtdService"); sgi0.endpoints.emplace_back(net::Address::fromString("127.0.0.1:9876")); info1.app.serviceGroups.push_back(sgi0); info1.set(flat::MgmtdHeartbeatInfo{}); info1.hbVersion = flat::HeartbeatVersion{1}; } // not primary CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); co_await MgmtdTestHelper(mgmtd).extendLease(); // forbid self heartbeat CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatFail, heartbeat(mgmtd, "clusterId", from(flat::NodeId(10000)), now_)); // timestamp deviation CO_AWAIT_ASSERT_ERROR( MgmtdCode::kHeartbeatFail, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_ + std::chrono::microseconds(2500))); // unregistered CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatFail, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); CO_AWAIT_ASSERT_OK(registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); // type mismatch with registered { auto info2 = info1; info2.set(flat::MetaHeartbeatInfo{}); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatFail, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info2), now_)); } // first successful heartbeat, status changed to CONNECTED { now_ = UtcTime::fromMicroseconds(10500); CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); auto node = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(node.base().type, flat::NodeType::MGMTD); CO_ASSERT_EQ(node.base().status, flat::NodeStatus::HEARTBEAT_CONNECTED); CO_ASSERT_EQ(node.base().app, info1.app) << fmt::format("node.app = {} info1.app = {}", serde::toJsonString(node.base().app), serde::toJsonString(info1.app)); CO_ASSERT_EQ(node.base().lastHeartbeatTs, now_); } // extend heartbeat: nothing changed { info1.hbVersion = flat::HeartbeatVersion{3}; auto node1 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); now_ = UtcTime::fromMicroseconds(10501); CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); auto node2 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(node2.base().lastHeartbeatTs, now_); // only lastHeartbeatTs differs node2.base().lastHeartbeatTs = node1.base().lastHeartbeatTs; CO_ASSERT_EQ(node1.base(), node2.base()); } // stale heartbeat version { info1.hbVersion = flat::HeartbeatVersion{2}; now_ = UtcTime::fromMicroseconds(10502); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatVersionStale, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); } // service info changed without restarting { auto info2 = info1; info2.hbVersion = flat::HeartbeatVersion{4}; info2.app.serviceGroups[0].services.emplace("OtherService"); CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info2), now_)); auto node = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(node.base().app, info2.app); } // restart: lastHeartbeatTs changed { auto node1 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); auto info2 = info1; info2.app.hostname = "hostname0-2"; info2.hbVersion = flat::HeartbeatVersion{1}; now_ = UtcTime::fromMicroseconds(10502); CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatVersionStale, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info2), now_)); info2.hbVersion = flat::HeartbeatVersion{5}; CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info2), now_)); auto node2 = co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)); CO_ASSERT_EQ(node2->base().type, node1.base().type); CO_ASSERT_EQ(node2->base().status, node1.base().status); CO_ASSERT_EQ(node2->base().app, info2.app); CO_ASSERT_EQ(node2->base().lastHeartbeatTs, now_); info2.app.pid = 102; info2.hbVersion = flat::HeartbeatVersion{6}; now_ = UtcTime::fromMicroseconds(10503); CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info2), now_)); node2 = co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)); CO_ASSERT_EQ(node2->base().type, node1.base().type); CO_ASSERT_EQ(node2->base().status, node1.base().status); CO_ASSERT_EQ(node2->base().app, info2.app); CO_ASSERT_EQ(node2->base().lastHeartbeatTs, now_); } // reconnected { auto &innerNode = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); innerNode.base().status = flat::NodeStatus::HEARTBEAT_FAILED; now_ = UtcTime::fromMicroseconds(10504); info1.hbVersion = flat::HeartbeatVersion{7}; CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); auto node = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(node.base().status, flat::NodeStatus::HEARTBEAT_CONNECTED); CO_ASSERT_EQ(node.base().lastHeartbeatTs, now_); } // reject heartbeats from offlined nodes { // TODO: migrate to a formal method auto &innerNode = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); innerNode.base().status = flat::NodeStatus::DISABLED; CO_AWAIT_ASSERT_ERROR(MgmtdCode::kHeartbeatFail, heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), info1), now_)); } // allow timestamp deviation and unregistered { auto options = defaultConfig_.clone(); options.set_heartbeat_timestamp_valid_window(0_ms); options.set_allow_heartbeat_from_unregistered(true); auto env = env_; env->setKvEngine(std::make_shared()); MgmtdOperator mgmtd1(std::move(env), options); now_ = UtcTime::fromMicroseconds(10000); co_await MgmtdTestHelper(mgmtd1).extendLease(); CO_AWAIT_ASSERT_OK( heartbeat(mgmtd1, "clusterId", from(flat::NodeId(1), info1), UtcTime::fromMicroseconds(12500))); auto node = *(co_await MgmtdTestHelper(mgmtd1).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(node.base().type, flat::NodeType::MGMTD); CO_ASSERT_EQ(node.base().status, flat::NodeStatus::HEARTBEAT_CONNECTED); CO_ASSERT_EQ(node.base().app, info1.app); CO_ASSERT_EQ(node.base().lastHeartbeatTs, now_); } }()); } TEST_F(MgmtdOperatorTest, testRecovery) { folly::coro::blockingWait([&]() -> CoTask { auto mgmtd1 = std::make_unique(env_, defaultConfig_); co_await MgmtdTestHelper(*mgmtd1).extendLease(); flat::HeartbeatInfo info1; { info1.app.hostname = "hostname1"; info1.app.pid = 101; flat::ServiceGroupInfo sgi0; sgi0.services.emplace("MgmtdService"); sgi0.endpoints.emplace_back(net::Address::fromString("127.0.0.1:9876")); info1.app.serviceGroups.push_back(sgi0); info1.set(flat::MgmtdHeartbeatInfo{}); } auto info2 = info1; info2.set(flat::MetaHeartbeatInfo{}); CO_AWAIT_ASSERT_OK(registerNode(*mgmtd1, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); CO_AWAIT_ASSERT_OK(registerNode(*mgmtd1, "clusterId", flat::NodeId(2), flat::NodeType::META)); CO_AWAIT_ASSERT_OK(registerNode(*mgmtd1, "clusterId", flat::NodeId(3), flat::NodeType::STORAGE)); now_ += std::chrono::microseconds(10); CO_AWAIT_ASSERT_OK(heartbeat(*mgmtd1, "clusterId", from(flat::NodeId(1), info1), now_)); CO_AWAIT_ASSERT_OK(heartbeat(*mgmtd1, "clusterId", from(flat::NodeId(2), info2), now_)); co_await disableNode(*mgmtd1, "clusterId", flat::NodeId(2)); mgmtd1.reset(); mgmtd1 = std::make_unique(env_, defaultConfig_); co_await MgmtdTestHelper(*mgmtd1).extendLease(); auto node = co_await MgmtdTestHelper(*mgmtd1).getNodeInfo(flat::NodeId(1)); CO_ASSERT_EQ(node->base().status, flat::NodeStatus::HEARTBEAT_CONNECTING); node = co_await MgmtdTestHelper(*mgmtd1).getNodeInfo(flat::NodeId(2)); CO_ASSERT_EQ(node->base().status, flat::NodeStatus::DISABLED); node = co_await MgmtdTestHelper(*mgmtd1).getNodeInfo(flat::NodeId(3)); CO_ASSERT_EQ(node->base().status, flat::NodeStatus::HEARTBEAT_CONNECTING); }()); } TEST_F(MgmtdOperatorTest, testDisable) { folly::coro::blockingWait([&]() -> CoTask { MgmtdOperator mgmtd(env_, defaultConfig_); // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, disableNode(mgmtd, "wrong", flat::NodeId(1))); // offline primary mgmtd CO_AWAIT_ASSERT_ERROR(StatusCode::kInvalidArg, disableNode(mgmtd, "clusterId", flat::NodeId(10000))); // not primary CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, disableNode(mgmtd, "clusterId", flat::NodeId(1))); co_await MgmtdTestHelper(mgmtd).extendLease(); // not registered CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNodeNotFound, disableNode(mgmtd, "clusterId", flat::NodeId(1))); CO_AWAIT_ASSERT_OK(registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); CO_AWAIT_ASSERT_OK(disableNode(mgmtd, "clusterId", flat::NodeId(1))); auto info1 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(info1.base().status, flat::NodeStatus::DISABLED); // duplicated offline has no effect CO_AWAIT_ASSERT_OK(disableNode(mgmtd, "clusterId", flat::NodeId(1))); auto info2 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(info1.base(), info2.base()); }()); } TEST_F(MgmtdOperatorTest, testEnable) { folly::coro::blockingWait([&]() -> CoTask { MgmtdOperator mgmtd(env_, defaultConfig_); // wrong clusterid CO_AWAIT_ASSERT_ERROR(MgmtdCode::kClusterIdMismatch, enableNode(mgmtd, "wrong", flat::NodeId(1))); // offline primary mgmtd CO_AWAIT_ASSERT_ERROR(StatusCode::kInvalidArg, enableNode(mgmtd, "clusterId", flat::NodeId(10000))); // not primaryA CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNotPrimary, enableNode(mgmtd, "clusterId", flat::NodeId(1))); co_await MgmtdTestHelper(mgmtd).extendLease(); // not registered CO_AWAIT_ASSERT_ERROR(MgmtdCode::kNodeNotFound, enableNode(mgmtd, "clusterId", flat::NodeId(1))); CO_AWAIT_ASSERT_OK(registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); flat::HeartbeatInfo hb; hb.set(flat::MgmtdHeartbeatInfo{}); CO_AWAIT_ASSERT_OK(heartbeat(mgmtd, "clusterId", from(flat::NodeId(1), hb), now_)); auto info1 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(info1.base().status, flat::NodeStatus::HEARTBEAT_CONNECTED); // online an online node has no effect CO_AWAIT_ASSERT_OK(enableNode(mgmtd, "clusterId", flat::NodeId(1))); auto info2 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(info1.base(), info2.base()); CO_AWAIT_ASSERT_OK(disableNode(mgmtd, "clusterId", flat::NodeId(1))); CO_AWAIT_ASSERT_OK(enableNode(mgmtd, "clusterId", flat::NodeId(1))); auto info3 = *(co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1))); CO_ASSERT_EQ(info3.base().status, flat::NodeStatus::HEARTBEAT_CONNECTING); }()); } TEST_F(MgmtdOperatorTest, testSetNodeTags) { auto f = [&]() -> CoTask { MgmtdOperator mgmtd(env_, defaultConfig_); co_await MgmtdTestHelper(mgmtd).extendLease(); CO_AWAIT_ASSERT_OK(registerNode(mgmtd, "clusterId", flat::NodeId(1), flat::NodeType::MGMTD)); auto info1 = (co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)))->base(); CO_ASSERT_TRUE(info1.tags.empty()); auto setRes = co_await setNodeTags(mgmtd, "clusterId", flat::NodeId(1), {flat::TagPair("A", "1"), flat::TagPair("B", "2"), flat::TagPair("C")}, flat::SetTagMode::UPSERT); CO_ASSERT_OK(setRes); auto expected = std::vector{flat::TagPair("A", "1"), flat::TagPair("B", "2"), flat::TagPair("C")}; info1 = (co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)))->base(); CO_ASSERT_EQ(setRes->info, info1); CO_ASSERT_EQ(info1.tags, expected); setRes = co_await setNodeTags(mgmtd, "clusterId", flat::NodeId(1), {flat::TagPair("B", "3"), flat::TagPair("D", "4")}, flat::SetTagMode::UPSERT); CO_ASSERT_OK(setRes); expected = std::vector{flat::TagPair("A", "1"), flat::TagPair("B", "3"), flat::TagPair("C"), flat::TagPair("D", "4")}; info1 = (co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)))->base(); CO_ASSERT_EQ(setRes->info, info1); CO_ASSERT_EQ(info1.tags, expected) << fmt::format("actual: \n{}\nexpectd: {}", serde::toJsonString(info1.tags), serde::toJsonString(expected)); setRes = co_await setNodeTags(mgmtd, "clusterId", flat::NodeId(1), {flat::TagPair("A", "2"), flat::TagPair("C", "3")}, flat::SetTagMode::REPLACE); CO_ASSERT_OK(setRes); expected = std::vector{flat::TagPair("A", "2"), flat::TagPair("C", "3")}; info1 = (co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)))->base(); CO_ASSERT_EQ(setRes->info, info1); CO_ASSERT_EQ(info1.tags, expected); setRes = co_await setNodeTags(mgmtd, "clusterId", flat::NodeId(1), {flat::TagPair("A", "1")}, flat::SetTagMode::REMOVE); CO_ASSERT_ERROR(setRes, MgmtdCode::kInvalidTag); setRes = co_await setNodeTags(mgmtd, "clusterId", flat::NodeId(1), {flat::TagPair("A")}, flat::SetTagMode::REMOVE); expected = std::vector{flat::TagPair("C", "3")}; info1 = (co_await MgmtdTestHelper(mgmtd).getNodeInfo(flat::NodeId(1)))->base(); CO_ASSERT_EQ(setRes->info, info1); }; folly::coro::blockingWait(f()); } } // namespace hf3fs::mgmtd::testing