diff --git a/ydb/core/viewer/json_compute.h b/ydb/core/viewer/json_compute.h index e4d9ef243646..4c6f0463c902 100644 --- a/ydb/core/viewer/json_compute.h +++ b/ydb/core/viewer/json_compute.h @@ -285,8 +285,8 @@ class TJsonCompute : public TViewerPipeClient { auto nodeId = nodeStat.GetNodeId(); if (IsRequiredNode(nodeId)) { const auto& nodeDomain = nodeStat.GetNodeDomain(); - TPathId subDomainKey(nodeDomain.GetSchemeShard(), nodeDomain.GetPathId()); - if (FilterSubDomain && FilterSubDomain != subDomainKey) { + const TPathId subDomain(nodeDomain.GetSchemeShard(), nodeDomain.GetPathId()); + if (FilterSubDomain && FilterSubDomain != subDomain) { continue; } NodeIds.emplace_back(nodeId); // order is important diff --git a/ydb/core/viewer/json_nodes.h b/ydb/core/viewer/json_nodes.h index 2db9b9a124aa..f663e38a3ad4 100644 --- a/ydb/core/viewer/json_nodes.h +++ b/ydb/core/viewer/json_nodes.h @@ -40,6 +40,7 @@ class TJsonNodes : public TViewerPipeClient { TJsonSettings JsonSettings; ui32 Timeout = 0; TString FilterTenant; + TSubDomainKey FilterSubDomainKey; TString FilterPath; TString FilterStoragePool; std::unordered_set FilterNodeIds; @@ -405,6 +406,11 @@ class TJsonNodes : public TViewerPipeClient { if (HiveId == 0) { HiveId = entry.DomainInfo->Params.GetHive(); } + if (!FilterSubDomainKey) { + const auto ownerId = entry.DomainInfo->DomainKey.OwnerId; + const auto localPathId = entry.DomainInfo->DomainKey.LocalPathId; + FilterSubDomainKey = TSubDomainKey(ownerId, localPathId); + } if (entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { TPathId resourceDomainKey(entry.DomainInfo->ResourcesDomainKey); BLOG_TRACE("Requesting navigate for resource domain " << resourceDomainKey); @@ -461,6 +467,10 @@ class TJsonNodes : public TViewerPipeClient { void Handle(TEvHive::TEvResponseHiveNodeStats::TPtr& ev) { BLOG_TRACE("ResponseHiveNodeStats()"); for (const NKikimrHive::THiveNodeStats& nodeStats : ev->Get()->Record.GetNodeStats()) { + const TSubDomainKey nodeSubDomainKey = TSubDomainKey(nodeStats.GetNodeDomain()); + if (FilterSubDomainKey && FilterSubDomainKey != nodeSubDomainKey) { + continue; + } ui32 nodeId = nodeStats.GetNodeId(); auto& tabletInfo(TabletInfo[nodeId]); for (const NKikimrHive::THiveDomainStatsStateCount& stateStats : nodeStats.GetStateStats()) { @@ -514,14 +524,18 @@ class TJsonNodes : public TViewerPipeClient { } void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); if (ev->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { + BLOG_TRACE("Received TEvBoardInfo"); for (const auto& [actorId, infoEntry] : ev->Get()->InfoEntries) { auto nodeId(actorId.NodeId()); BLOG_TRACE("BoardInfo filter node by " << nodeId); FilterNodeIds.insert(nodeId); } + } else { + BLOG_TRACE("Error receiving TEvBoardInfo response"); + FilterNodeIds = { 0 }; } + if (--RequestsBeforeNodeList == 0) { ProcessNodeIds(); } diff --git a/ydb/core/viewer/json_tenantinfo.h b/ydb/core/viewer/json_tenantinfo.h index 55abe40afe11..d507ff2a8b2f 100644 --- a/ydb/core/viewer/json_tenantinfo.h +++ b/ydb/core/viewer/json_tenantinfo.h @@ -667,7 +667,8 @@ class TJsonTenantInfo : public TViewerPipeClient { if (tenant.GetType() == NKikimrViewer::Serverless) { tenant.SetStorageAllocatedSize(tenant.GetMetrics().GetStorage()); - if (tenant.NodeIdsSize() == 0) { + const bool noExclusiveNodes = tenantNodes.empty(); + if (noExclusiveNodes) { tenant.SetMemoryUsed(tenant.GetMetrics().GetMemory()); tenant.ClearMemoryLimit(); tenant.SetCoresUsed(static_cast(tenant.GetMetrics().GetCPU()) / 1000000); diff --git a/ydb/core/viewer/viewer_ut.cpp b/ydb/core/viewer/viewer_ut.cpp index 40c989d9f65a..16ed1a79d0d4 100644 --- a/ydb/core/viewer/viewer_ut.cpp +++ b/ydb/core/viewer/viewer_ut.cpp @@ -600,4 +600,436 @@ Y_UNIT_TEST_SUITE(Viewer) { StorageSpaceTest("space", NKikimrWhiteboard::EFlag::Green, 80, 100, true); StorageSpaceTest("space", NKikimrWhiteboard::EFlag::Green, 90, 100, true); } + + Y_UNIT_TEST(ServerlessNodesPage) + { + TPortManager tp; + ui16 port = tp.GetPort(2134); + ui16 grpcPort = tp.GetPort(2135); + auto settings = TServerSettings(port) + .SetNodeCount(1) + .SetUseRealThreads(false) + .SetDomainName("Root") + .InitKikimrRunConfig(); + TServer server(settings); + server.EnableGRpc(grpcPort); + + TClient client(settings); + TTestActorRuntime& runtime = *server.GetRuntime(); + runtime.GetAppData().DynamicNameserviceConfig->MaxStaticNodeId = 0; + + TActorId sender = runtime.AllocateEdgeActor(); + TAutoPtr handle; + + THttpRequest httpReq(HTTP_METHOD_GET); + httpReq.CgiParameters.emplace("tenant", "/Root/serverless"); + httpReq.CgiParameters.emplace("tablets", "true"); + httpReq.CgiParameters.emplace("enums", "true"); + httpReq.CgiParameters.emplace("sort", ""); + httpReq.CgiParameters.emplace("type", "any"); + auto page = MakeHolder("viewer", "title"); + TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); + auto request = MakeHolder(monReq); + + bool firstNavigateResponse = true; + auto observerFunc = [&](TAutoPtr& ev) { + switch (ev->GetTypeRewrite()) { + case TEvTxProxySchemeCache::EvNavigateKeySetResult: { + auto *x = reinterpret_cast(&ev); + TSchemeCacheNavigate::TEntry& entry((*x)->Get()->Request->ResultSet.front()); + if (firstNavigateResponse) { + firstNavigateResponse = false; + entry.Status = TSchemeCacheNavigate::EStatus::Ok; + entry.Path = {"Root", "serverless"}; + entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; + entry.DomainInfo->DomainKey = {7000000000, 2}; + entry.DomainInfo->ResourcesDomainKey = {7000000000, 1}; + } else { + entry.Status = TSchemeCacheNavigate::EStatus::Ok; + entry.Path = {"Root", "shared"}; + entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; + entry.DomainInfo->DomainKey = {7000000000, 1}; + entry.DomainInfo->ResourcesDomainKey = {7000000000, 1}; + entry.DomainInfo->Params.SetHive(NKikimr::Tests::Hive); + } + break; + } + case TEvHive::EvResponseHiveNodeStats: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + auto *nodeStats = record.MutableNodeStats()->Add(); + nodeStats->SetNodeId(1); + auto *stateStats = nodeStats->MutableStateStats()->Add(); + stateStats->SetTabletType(NKikimrTabletBase::TTabletTypes::DataShard); + stateStats->SetVolatileState(NKikimrHive::TABLET_VOLATILE_STATE_RUNNING); + stateStats->SetCount(1); + break; + } + case TEvWhiteboard::EvTabletStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + auto tablet = record.AddTabletStateInfo(); + tablet->SetTabletId(100); + tablet->SetType(NKikimrTabletBase::TTabletTypes::DataShard); + tablet->SetNodeId(1); + tablet->SetGeneration(2); + break; + } + case TEvInterconnect::EvNodesInfo: { + auto *x = reinterpret_cast(&ev); + auto &nodes = (*x)->Get()->Nodes; + nodes.clear(); + TEvInterconnect::TNodeInfo node; + node.NodeId = 1; + nodes.push_back(node); + break; + } + case TEvWhiteboard::EvSystemStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + auto *systemStateInfo = record.AddSystemStateInfo(); + systemStateInfo->SetHost("host.yandex.net"); + break; + } + case TEvStateStorage::EvBoardInfo: { + auto *x = reinterpret_cast(&ev); + auto *record = (*x)->Get(); + using EStatus = TEvStateStorage::TEvBoardInfo::EStatus; + const_cast(record->Status) = EStatus::NotAvailable; + record->InfoEntries.clear(); + break; + } + } + + return TTestActorRuntime::EEventAction::PROCESS; + }; + runtime.SetObserverFunc(observerFunc); + + runtime.Send(new IEventHandle(NKikimr::NViewer::MakeViewerID(0), sender, request.Release(), 0)); + NMon::TEvHttpInfoRes* result = runtime.GrabEdgeEvent(handle); + + size_t pos = result->Answer.find('{'); + TString jsonResult = result->Answer.substr(pos); + Ctest << "json result: " << jsonResult << Endl; + NJson::TJsonValue json; + try { + NJson::ReadJsonTree(jsonResult, &json, true); + } + catch (yexception ex) { + Ctest << ex.what() << Endl; + } + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("TotalNodes"), "0"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("FoundNodes"), "0"); + } + + Y_UNIT_TEST(ServerlessWithExclusiveNodes) + { + TPortManager tp; + ui16 port = tp.GetPort(2134); + ui16 grpcPort = tp.GetPort(2135); + auto settings = TServerSettings(port) + .SetNodeCount(2) + .SetUseRealThreads(false) + .SetDomainName("Root") + .InitKikimrRunConfig(); + TServer server(settings); + server.EnableGRpc(grpcPort); + + TClient client(settings); + TTestActorRuntime& runtime = *server.GetRuntime(); + runtime.GetAppData().DynamicNameserviceConfig->MaxStaticNodeId = 0; + + TActorId sender = runtime.AllocateEdgeActor(); + TAutoPtr handle; + + THttpRequest httpReq(HTTP_METHOD_GET); + httpReq.CgiParameters.emplace("tenant", "/Root/serverless"); + httpReq.CgiParameters.emplace("tablets", "true"); + httpReq.CgiParameters.emplace("enums", "true"); + httpReq.CgiParameters.emplace("sort", ""); + httpReq.CgiParameters.emplace("type", "any"); + auto page = MakeHolder("viewer", "title"); + TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); + auto request = MakeHolder(monReq); + + bool firstNavigateResponse = true; + auto observerFunc = [&](TAutoPtr& ev) { + switch (ev->GetTypeRewrite()) { + case TEvTxProxySchemeCache::EvNavigateKeySetResult: { + auto *x = reinterpret_cast(&ev); + TSchemeCacheNavigate::TEntry& entry((*x)->Get()->Request->ResultSet.front()); + if (firstNavigateResponse) { + firstNavigateResponse = false; + entry.Status = TSchemeCacheNavigate::EStatus::Ok; + entry.Path = {"Root", "serverless"}; + entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; + entry.DomainInfo->DomainKey = {7000000000, 2}; + entry.DomainInfo->ResourcesDomainKey = {7000000000, 1}; + } else { + entry.Status = TSchemeCacheNavigate::EStatus::Ok; + entry.Path = {"Root", "shared"}; + entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; + entry.DomainInfo->DomainKey = {7000000000, 1}; + entry.DomainInfo->ResourcesDomainKey = {7000000000, 1}; + entry.DomainInfo->Params.SetHive(NKikimr::Tests::Hive); + } + break; + } + case TEvHive::EvResponseHiveNodeStats: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + auto *nodeStats = record.MutableNodeStats()->Add(); + nodeStats->SetNodeId(1); + auto *stateStats = nodeStats->MutableStateStats()->Add(); + stateStats->SetTabletType(NKikimrTabletBase::TTabletTypes::DataShard); + stateStats->SetVolatileState(NKikimrHive::TABLET_VOLATILE_STATE_RUNNING); + stateStats->SetCount(1); + + nodeStats = record.MutableNodeStats()->Add(); + nodeStats->SetNodeId(2); + stateStats = nodeStats->MutableStateStats()->Add(); + stateStats->SetTabletType(NKikimrTabletBase::TTabletTypes::Coordinator); + stateStats->SetVolatileState(NKikimrHive::TABLET_VOLATILE_STATE_RUNNING); + stateStats->SetCount(1); + break; + } + case TEvWhiteboard::EvTabletStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + if ((*x)->Cookie == 1) { + auto tablet = record.AddTabletStateInfo(); + tablet->SetType(NKikimrTabletBase::TTabletTypes::DataShard); + tablet->SetState(NKikimrWhiteboard::TTabletStateInfo::Active); + tablet->SetCount(1); + tablet->SetNodeId(1); + } else if ((*x)->Cookie == 2) { + auto tablet = record.AddTabletStateInfo(); + tablet->SetType(NKikimrTabletBase::TTabletTypes::Coordinator); + tablet->SetState(NKikimrWhiteboard::TTabletStateInfo::Active); + tablet->SetCount(1); + tablet->SetNodeId(2); + } + break; + } + case TEvInterconnect::EvNodesInfo: { + auto *x = reinterpret_cast(&ev); + auto &nodes = (*x)->Get()->Nodes; + nodes.clear(); + TEvInterconnect::TNodeInfo node; + node.NodeId = 1; + nodes.push_back(node); + TEvInterconnect::TNodeInfo exclusiveNode; + exclusiveNode.NodeId = 2; + nodes.push_back(exclusiveNode); + break; + } + case TEvWhiteboard::EvSystemStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + if ((*x)->Cookie == 1) { + auto *systemStateInfo = record.AddSystemStateInfo(); + systemStateInfo->SetHost("host.yandex.net"); + } else if ((*x)->Cookie == 2) { + auto *systemStateInfo = record.AddSystemStateInfo(); + systemStateInfo->SetHost("exclusive.host.yandex.net"); + } + break; + } + case TEvStateStorage::EvBoardInfo: { + auto *x = reinterpret_cast(&ev); + auto *record = (*x)->Get(); + using EStatus = TEvStateStorage::TEvBoardInfo::EStatus; + const_cast(record->Status) = EStatus::Ok; + record->InfoEntries[TActorId(2, 0, 0, 0)] = {}; + break; + } + } + + return TTestActorRuntime::EEventAction::PROCESS; + }; + runtime.SetObserverFunc(observerFunc); + + runtime.Send(new IEventHandle(NKikimr::NViewer::MakeViewerID(0), sender, request.Release(), 0)); + NMon::TEvHttpInfoRes* result = runtime.GrabEdgeEvent(handle); + + size_t pos = result->Answer.find('{'); + TString jsonResult = result->Answer.substr(pos); + Ctest << "json result: " << jsonResult << Endl; + NJson::TJsonValue json; + try { + NJson::ReadJsonTree(jsonResult, &json, true); + } + catch (yexception ex) { + Ctest << ex.what() << Endl; + } + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("TotalNodes"), "1"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("FoundNodes"), "1"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("Nodes").GetArray().size(), 1); + auto node = json.GetMap().at("Nodes").GetArray()[0].GetMap(); + UNIT_ASSERT_VALUES_EQUAL(node.at("NodeId"), 2); + UNIT_ASSERT_VALUES_EQUAL(node.at("SystemState").GetMap().at("Host"), "exclusive.host.yandex.net"); + UNIT_ASSERT_VALUES_EQUAL(node.at("Tablets").GetArray().size(), 1); + auto tablet = node.at("Tablets").GetArray()[0].GetMap(); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("Count"), 1); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("State"), "Green"); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("Type"), "Coordinator"); + } + + Y_UNIT_TEST(SharedDoesntShowExclusiveNodes) + { + TPortManager tp; + ui16 port = tp.GetPort(2134); + ui16 grpcPort = tp.GetPort(2135); + auto settings = TServerSettings(port) + .SetNodeCount(2) + .SetUseRealThreads(false) + .SetDomainName("Root") + .InitKikimrRunConfig(); + TServer server(settings); + server.EnableGRpc(grpcPort); + + TClient client(settings); + TTestActorRuntime& runtime = *server.GetRuntime(); + runtime.GetAppData().DynamicNameserviceConfig->MaxStaticNodeId = 0; + + TActorId sender = runtime.AllocateEdgeActor(); + TAutoPtr handle; + + THttpRequest httpReq(HTTP_METHOD_GET); + httpReq.CgiParameters.emplace("tenant", "Root/shared"); + httpReq.CgiParameters.emplace("tablets", "true"); + httpReq.CgiParameters.emplace("enums", "true"); + httpReq.CgiParameters.emplace("sort", ""); + httpReq.CgiParameters.emplace("type", "any"); + auto page = MakeHolder("viewer", "title"); + TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); + auto request = MakeHolder(monReq); + + auto observerFunc = [&](TAutoPtr& ev) { + switch (ev->GetTypeRewrite()) { + case TEvTxProxySchemeCache::EvNavigateKeySetResult: { + auto *x = reinterpret_cast(&ev); + TSchemeCacheNavigate::TEntry& entry((*x)->Get()->Request->ResultSet.front()); + entry.Status = TSchemeCacheNavigate::EStatus::Ok; + entry.Path = {"Root", "shared"}; + entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; + entry.DomainInfo->DomainKey = {7000000000, 1}; + entry.DomainInfo->ResourcesDomainKey = {7000000000, 1}; + entry.DomainInfo->Params.SetHive(NKikimr::Tests::Hive); + break; + } + case TEvHive::EvResponseHiveNodeStats: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + auto *nodeStats = record.MutableNodeStats()->Add(); + nodeStats->SetNodeId(1); + auto *stateStats = nodeStats->MutableStateStats()->Add(); + stateStats->SetTabletType(NKikimrTabletBase::TTabletTypes::DataShard); + stateStats->SetVolatileState(NKikimrHive::TABLET_VOLATILE_STATE_RUNNING); + stateStats->SetCount(1); + + nodeStats = record.MutableNodeStats()->Add(); + nodeStats->SetNodeId(2); + stateStats = nodeStats->MutableStateStats()->Add(); + stateStats->SetTabletType(NKikimrTabletBase::TTabletTypes::Coordinator); + stateStats->SetVolatileState(NKikimrHive::TABLET_VOLATILE_STATE_RUNNING); + stateStats->SetCount(1); + break; + } + case TEvWhiteboard::EvTabletStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + + if ((*x)->Cookie == 1) { + auto tablet = record.AddTabletStateInfo(); + tablet->SetType(NKikimrTabletBase::TTabletTypes::DataShard); + tablet->SetState(NKikimrWhiteboard::TTabletStateInfo::Active); + tablet->SetCount(1); + tablet->SetNodeId(1); + } else if ((*x)->Cookie == 2) { + auto tablet = record.AddTabletStateInfo(); + tablet->SetType(NKikimrTabletBase::TTabletTypes::Coordinator); + tablet->SetState(NKikimrWhiteboard::TTabletStateInfo::Active); + tablet->SetCount(1); + tablet->SetNodeId(2); + } + break; + } + case TEvInterconnect::EvNodesInfo: { + auto *x = reinterpret_cast(&ev); + auto &nodes = (*x)->Get()->Nodes; + nodes.clear(); + TEvInterconnect::TNodeInfo node; + node.NodeId = 1; + nodes.push_back(node); + TEvInterconnect::TNodeInfo exclusiveNode; + exclusiveNode.NodeId = 2; + nodes.push_back(exclusiveNode); + break; + } + case TEvWhiteboard::EvSystemStateResponse: { + auto *x = reinterpret_cast(&ev); + auto &record = (*x)->Get()->Record; + record.Clear(); + if ((*x)->Cookie == 1) { + auto *systemStateInfo = record.AddSystemStateInfo(); + systemStateInfo->SetHost("host.yandex.net"); + } else if ((*x)->Cookie == 2) { + auto *systemStateInfo = record.AddSystemStateInfo(); + systemStateInfo->SetHost("exclusive.host.yandex.net"); + } + break; + } + case TEvStateStorage::EvBoardInfo: { + auto *x = reinterpret_cast(&ev); + auto *record = (*x)->Get(); + using EStatus = TEvStateStorage::TEvBoardInfo::EStatus; + const_cast(record->Status) = EStatus::Ok; + record->InfoEntries[TActorId(1, 0, 0, 0)] = {}; + break; + } + } + + return TTestActorRuntime::EEventAction::PROCESS; + }; + runtime.SetObserverFunc(observerFunc); + + runtime.Send(new IEventHandle(NKikimr::NViewer::MakeViewerID(0), sender, request.Release(), 0)); + NMon::TEvHttpInfoRes* result = runtime.GrabEdgeEvent(handle); + + size_t pos = result->Answer.find('{'); + TString jsonResult = result->Answer.substr(pos); + Ctest << "json result: " << jsonResult << Endl; + NJson::TJsonValue json; + try { + NJson::ReadJsonTree(jsonResult, &json, true); + } + catch (yexception ex) { + Ctest << ex.what() << Endl; + } + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("TotalNodes"), "1"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("FoundNodes"), "1"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("Nodes").GetArray().size(), 1); + auto node = json.GetMap().at("Nodes").GetArray()[0].GetMap(); + UNIT_ASSERT_VALUES_EQUAL(node.at("NodeId"), 1); + UNIT_ASSERT_VALUES_EQUAL(node.at("SystemState").GetMap().at("Host"), "host.yandex.net"); + UNIT_ASSERT_VALUES_EQUAL(node.at("Tablets").GetArray().size(), 1); + auto tablet = node.at("Tablets").GetArray()[0].GetMap(); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("Count"), 1); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("State"), "Green"); + UNIT_ASSERT_VALUES_EQUAL(tablet.at("Type"), "DataShard"); + } }