diff --git a/ydb/core/base/board_lookup.cpp b/ydb/core/base/board_lookup.cpp index 634cc5dc064e..e3a6e7df35b5 100644 --- a/ydb/core/base/board_lookup.cpp +++ b/ydb/core/base/board_lookup.cpp @@ -25,6 +25,7 @@ namespace NKikimr { class TBoardLookupActor : public TActorBootstrapped { const TString Path; const TActorId Owner; + const ui64 Cookie; const EBoardLookupMode Mode; const bool Subscriber; TBoardRetrySettings BoardRetrySettings; @@ -111,12 +112,12 @@ class TBoardLookupActor : public TActorBootstrapped { void NotAvailable() { if (CurrentStateFunc() != &TThis::StateSubscribe) { Send(Owner, new TEvStateStorage::TEvBoardInfo( - TEvStateStorage::TEvBoardInfo::EStatus::NotAvailable, Path)); + TEvStateStorage::TEvBoardInfo::EStatus::NotAvailable, Path), 0, Cookie); } else { Send(Owner, new TEvStateStorage::TEvBoardInfoUpdate( TEvStateStorage::TEvBoardInfo::EStatus::NotAvailable, Path - ) + ), 0, Cookie ); } return PassAway(); @@ -129,7 +130,7 @@ class TBoardLookupActor : public TActorBootstrapped { auto reply = MakeHolder( TEvStateStorage::TEvBoardInfo::EStatus::Ok, Path); reply->InfoEntries = std::move(Info); - Send(Owner, std::move(reply)); + Send(Owner, std::move(reply), 0, Cookie); if (Subscriber) { Become(&TThis::StateSubscribe); return; @@ -240,7 +241,7 @@ class TBoardLookupActor : public TActorBootstrapped { auto reply = MakeHolder( TEvStateStorage::TEvBoardInfo::EStatus::Ok, Path); reply->Updates = { { oid, std::move(update.value()) } }; - Send(Owner, std::move(reply)); + Send(Owner, std::move(reply), 0, Cookie); } } else { if (info.GetDropped()) { @@ -308,7 +309,7 @@ class TBoardLookupActor : public TActorBootstrapped { auto reply = MakeHolder( TEvStateStorage::TEvBoardInfo::EStatus::Ok, Path); reply->Updates = std::move(updates); - Send(Owner, std::move(reply)); + Send(Owner, std::move(reply), 0, Cookie); } } @@ -484,7 +485,7 @@ class TBoardLookupActor : public TActorBootstrapped { auto reply = MakeHolder( TEvStateStorage::TEvBoardInfo::EStatus::Ok, Path); reply->Updates = std::move(updates); - Send(Owner, std::move(reply)); + Send(Owner, std::move(reply), 0, Cookie); } } @@ -495,9 +496,10 @@ class TBoardLookupActor : public TActorBootstrapped { TBoardLookupActor( const TString &path, TActorId owner, EBoardLookupMode mode, - TBoardRetrySettings boardRetrySettings) + TBoardRetrySettings boardRetrySettings, ui64 cookie = 0) : Path(path) , Owner(owner) + , Cookie(cookie) , Mode(mode) , Subscriber(Mode == EBoardLookupMode::Subscription) , BoardRetrySettings(std::move(boardRetrySettings)) @@ -545,8 +547,8 @@ class TBoardLookupActor : public TActorBootstrapped { IActor* CreateBoardLookupActor( const TString &path, const TActorId &owner, EBoardLookupMode mode, - TBoardRetrySettings boardRetrySettings) { - return new TBoardLookupActor(path, owner, mode, std::move(boardRetrySettings)); + TBoardRetrySettings boardRetrySettings, ui64 cookie) { + return new TBoardLookupActor(path, owner, mode, std::move(boardRetrySettings), cookie); } } diff --git a/ydb/core/base/statestorage.h b/ydb/core/base/statestorage.h index 1c609a702d40..6a1726a3fb3b 100644 --- a/ydb/core/base/statestorage.h +++ b/ydb/core/base/statestorage.h @@ -556,7 +556,7 @@ IActor* CreateStateStorageBoardReplica(const TIntrusivePtr &, IActor* CreateSchemeBoardReplica(const TIntrusivePtr&, ui32); IActor* CreateBoardLookupActor( const TString &path, const TActorId &owner, EBoardLookupMode mode, - TBoardRetrySettings boardRetrySettings = {}); + TBoardRetrySettings boardRetrySettings = {}, ui64 cookie = 0); IActor* CreateBoardPublishActor( const TString &path, const TString &payload, const TActorId &owner, ui32 ttlMs, bool reg, TBoardRetrySettings boardRetrySettings = {}); diff --git a/ydb/core/blobstorage/pdisk/blobstorage_pdisk_impl.cpp b/ydb/core/blobstorage/pdisk/blobstorage_pdisk_impl.cpp index fc0c597a99ca..2ac80067655a 100644 --- a/ydb/core/blobstorage/pdisk/blobstorage_pdisk_impl.cpp +++ b/ydb/core/blobstorage/pdisk/blobstorage_pdisk_impl.cpp @@ -1477,7 +1477,7 @@ void TPDisk::WhiteboardReport(TWhiteboardReport &whiteboardReport) { TGuard guard(StateMutex); const ui64 totalSize = Format.DiskSize; const ui64 availableSize = (ui64)Format.ChunkSize * Keeper.GetFreeChunkCount(); - + if (*Mon.PDiskBriefState != TPDiskMon::TPDisk::Error) { *Mon.FreeSpaceBytes = availableSize; *Mon.UsedSpaceBytes = totalSize - availableSize; @@ -1487,7 +1487,7 @@ void TPDisk::WhiteboardReport(TWhiteboardReport &whiteboardReport) { *Mon.UsedSpaceBytes = 32_KB; *Mon.TotalSpaceBytes = 32_KB; } - + NKikimrWhiteboard::TPDiskStateInfo& pdiskState = reportResult->PDiskState->Record; pdiskState.SetPDiskId(PDiskId); pdiskState.SetPath(Cfg->GetDevicePath()); @@ -1499,6 +1499,7 @@ void TPDisk::WhiteboardReport(TWhiteboardReport &whiteboardReport) { pdiskState.SetSystemSize(Format.ChunkSize * (Keeper.GetOwnerHardLimit(OwnerSystemLog) + Keeper.GetOwnerHardLimit(OwnerSystemReserve))); pdiskState.SetLogUsedSize(Format.ChunkSize * (Keeper.GetOwnerHardLimit(OwnerCommonStaticLog) - Keeper.GetOwnerFree(OwnerCommonStaticLog))); pdiskState.SetLogTotalSize(Format.ChunkSize * Keeper.GetOwnerHardLimit(OwnerCommonStaticLog)); + pdiskState.SetNumActiveSlots(TotalOwners); if (ExpectedSlotCount) { pdiskState.SetExpectedSlotCount(ExpectedSlotCount); } diff --git a/ydb/core/change_exchange/change_sender_common_ops.h b/ydb/core/change_exchange/change_sender_common_ops.h index 823b208f5323..f2c02c0b595b 100644 --- a/ydb/core/change_exchange/change_sender_common_ops.h +++ b/ydb/core/change_exchange/change_sender_common_ops.h @@ -413,8 +413,10 @@ class TBaseChangeSender { } TActorId GetChangeServer() const { return ChangeServer; } - void CreateSenders(const TVector& partitionIds, bool partitioningChanged = true) { - if (partitioningChanged) { + +private: + void CreateSendersImpl(const TVector& partitionIds) { + if (partitionIds) { CreateMissingSenders(partitionIds); } else { RecreateSenders(GonePartitions); @@ -427,6 +429,16 @@ class TBaseChangeSender { } } +protected: + void CreateSenders(const TVector& partitionIds) { + Y_ABORT_UNLESS(partitionIds); + CreateSendersImpl(partitionIds); + } + + void CreateSenders() { + CreateSendersImpl({}); + } + void KillSenders() { for (const auto& [_, sender] : std::exchange(Senders, {})) { if (sender.ActorId) { diff --git a/ydb/core/change_exchange/util.cpp b/ydb/core/change_exchange/util.cpp new file mode 100644 index 000000000000..c4c0516e0e94 --- /dev/null +++ b/ydb/core/change_exchange/util.cpp @@ -0,0 +1,15 @@ +#include "util.h" + +namespace NKikimr::NChangeExchange { + +TVector MakePartitionIds(const TVector& partitions) { + TVector result(::Reserve(partitions.size())); + + for (const auto& partition : partitions) { + result.push_back(partition.ShardId); + } + + return result; +} + +} diff --git a/ydb/core/change_exchange/util.h b/ydb/core/change_exchange/util.h new file mode 100644 index 000000000000..f8ba146fdeaf --- /dev/null +++ b/ydb/core/change_exchange/util.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace NKikimr::NChangeExchange { + +TVector MakePartitionIds(const TVector& partitions); + +} diff --git a/ydb/core/change_exchange/ya.make b/ydb/core/change_exchange/ya.make index b95ab2178442..680c246118ea 100644 --- a/ydb/core/change_exchange/ya.make +++ b/ydb/core/change_exchange/ya.make @@ -4,6 +4,7 @@ SRCS( change_exchange.cpp change_record.cpp change_sender_monitoring.cpp + util.cpp ) GENERATE_ENUM_SERIALIZATION(change_record.h) diff --git a/ydb/core/cms/api_adapters.cpp b/ydb/core/cms/api_adapters.cpp index 3e141a741122..2b2d37258423 100644 --- a/ydb/core/cms/api_adapters.cpp +++ b/ydb/core/cms/api_adapters.cpp @@ -54,11 +54,33 @@ namespace { } } + Ydb::Maintenance::ActionState::ActionReason ConvertReason(NKikimrCms::TAction::TIssue::EType cmsActionIssueType) { + using EIssueType = NKikimrCms::TAction::TIssue; + switch (cmsActionIssueType) { + case EIssueType::UNKNOWN: + return Ydb::Maintenance::ActionState::ACTION_REASON_UNSPECIFIED; + case EIssueType::GENERIC: + return Ydb::Maintenance::ActionState::ACTION_REASON_GENERIC; + case EIssueType::TOO_MANY_UNAVAILABLE_VDISKS: + return Ydb::Maintenance::ActionState::ACTION_REASON_TOO_MANY_UNAVAILABLE_VDISKS; + case EIssueType::TOO_MANY_UNAVAILABLE_STATE_STORAGE_RINGS: + return Ydb::Maintenance::ActionState::ACTION_REASON_TOO_MANY_UNAVAILABLE_STATE_STORAGE_RINGS; + case EIssueType::DISABLED_NODES_LIMIT_REACHED: + return Ydb::Maintenance::ActionState::ACTION_REASON_DISABLED_NODES_LIMIT_REACHED; + case EIssueType::TENANT_DISABLED_NODES_LIMIT_REACHED: + return Ydb::Maintenance::ActionState::ACTION_REASON_TENANT_DISABLED_NODES_LIMIT_REACHED; + case EIssueType::SYS_TABLETS_NODE_LIMIT_REACHED: + return Ydb::Maintenance::ActionState::ACTION_REASON_SYS_TABLETS_NODE_LIMIT_REACHED; + } + return Ydb::Maintenance::ActionState::ACTION_REASON_UNSPECIFIED; + } + void ConvertAction(const NKikimrCms::TAction& cmsAction, Ydb::Maintenance::ActionState& actionState) { ConvertAction(cmsAction, *actionState.mutable_action()->mutable_lock_action()); // FIXME: specify action_uid actionState.set_status(Ydb::Maintenance::ActionState::ACTION_STATUS_PENDING); - actionState.set_reason(Ydb::Maintenance::ActionState::ACTION_REASON_UNSPECIFIED); // FIXME: specify + actionState.set_reason(ConvertReason(cmsAction.GetIssue().GetType())); + actionState.set_reason_details(cmsAction.GetIssue().GetMessage()); } void ConvertActionUid(const TString& taskUid, const TString& permissionId, diff --git a/ydb/core/cms/cluster_info.h b/ydb/core/cms/cluster_info.h index de2c85473518..19fea924fe1a 100644 --- a/ydb/core/cms/cluster_info.h +++ b/ydb/core/cms/cluster_info.h @@ -37,13 +37,6 @@ using TClusterInfoPtr = TIntrusivePtr; struct TCmsState; using TCmsStatePtr = TIntrusivePtr; -struct TErrorInfo { - NKikimrCms::TStatus::ECode Code = NKikimrCms::TStatus::ALLOW; - TString Reason; - TInstant Deadline; - ui64 RollbackPoint = 0; -}; - /** * Structure to hold info about issued permission. A set of * all issued permissions is a part of CMS persistent state. diff --git a/ydb/core/cms/cms.cpp b/ydb/core/cms/cms.cpp index 5c1cf97ab814..c2dacd311246 100644 --- a/ydb/core/cms/cms.cpp +++ b/ydb/core/cms/cms.cpp @@ -36,6 +36,38 @@ namespace NKikimr::NCms { using namespace NNodeWhiteboard; using namespace NKikimrCms; +namespace { + +constexpr size_t MAX_ISSUES_TO_STORE = 100; + +TAction::TIssue ConvertIssue(const TReason& reason) { + TAction::TIssue issue; + switch (reason.GetType()) { + case TReason::EType::Generic: + issue.SetType(TAction::TIssue::GENERIC); + break; + case TReason::EType::TooManyUnavailableVDisks: + issue.SetType(TAction::TIssue::TOO_MANY_UNAVAILABLE_VDISKS); + break; + case TReason::EType::TooManyUnavailableStateStorageRings: + issue.SetType(TAction::TIssue::TOO_MANY_UNAVAILABLE_STATE_STORAGE_RINGS); + break; + case TReason::EType::DisabledNodesLimitReached: + issue.SetType(TAction::TIssue::DISABLED_NODES_LIMIT_REACHED); + break; + case TReason::EType::TenantDisabledNodesLimitReached: + issue.SetType(TAction::TIssue::TENANT_DISABLED_NODES_LIMIT_REACHED); + break; + case TReason::EType::SysTabletsNodeLimitReached: + issue.SetType(TAction::TIssue::SYS_TABLETS_NODE_LIMIT_REACHED); + break; + } + issue.SetMessage(reason.GetMessage()); + return issue; +} + +} // anonymous namespace + void TCms::DefaultSignalTabletActive(const TActorContext &) { // must be empty @@ -326,6 +358,8 @@ bool TCms::CheckPermissionRequest(const TPermissionRequest &request, }; auto point = ClusterInfo->PushRollbackPoint(); + size_t storedIssues = 0; + size_t processedActions = 0; for (const auto &action : request.GetActions()) { TDuration permissionDuration = State->Config.DefaultPermissionDuration; if (request.HasDuration()) @@ -352,28 +386,40 @@ bool TCms::CheckPermissionRequest(const TPermissionRequest &request, auto *permission = response.AddPermissions(); permission->MutableAction()->CopyFrom(action); + permission->MutableAction()->ClearIssue(); permission->SetDeadline(error.Deadline.GetValue()); AddPermissionExtensions(action, *permission); ClusterInfo->AddTempLocks(action, &ctx); } else { LOG_DEBUG(ctx, NKikimrServices::CMS, "Result: %s (reason: %s)", - ToString(error.Code).data(), error.Reason.data()); + ToString(error.Code).data(), error.Reason.GetMessage().data()); if (CodesRate[response.GetStatus().GetCode()] > CodesRate[error.Code]) { response.MutableStatus()->SetCode(error.Code); - response.MutableStatus()->SetReason(error.Reason); + response.MutableStatus()->SetReason(error.Reason.GetMessage()); if (error.Code == TStatus::DISALLOW_TEMP || error.Code == TStatus::ERROR_TEMP) response.SetDeadline(error.Deadline.GetValue()); } + if (schedule) { + auto *scheduledAction = scheduled.AddActions(); + scheduledAction->CopyFrom(action); + + // Limit stored issues to avoid overloading the local database + if (storedIssues < MAX_ISSUES_TO_STORE) { + *scheduledAction->MutableIssue() = ConvertIssue(error.Reason); + ++storedIssues; + } else { + scheduledAction->ClearIssue(); + } + } + if (!allowPartial) break; - - if (schedule) - scheduled.AddActions()->CopyFrom(action); } + ++processedActions; } ClusterInfo->RollbackLocks(point); @@ -396,9 +442,21 @@ bool TCms::CheckPermissionRequest(const TPermissionRequest &request, if (schedule && response.GetStatus().GetCode() != TStatus::ALLOW_PARTIAL) { if (response.GetStatus().GetCode() == TStatus::DISALLOW_TEMP || response.GetStatus().GetCode() == TStatus::ERROR_TEMP) - scheduled.MutableActions()->CopyFrom(request.GetActions()); - else + { + if (!allowPartial) { + // Only the first problem action was scheduled during + // the actions check loop. Merge it with rest actions. + Y_ABORT_UNLESS(scheduled.ActionsSize() == 1); + TAction::TIssue issue = std::move(*scheduled.MutableActions()->begin()->MutableIssue()); + scheduled.MutableActions()->CopyFrom(request.GetActions()); + for (auto &action : *scheduled.MutableActions()) { + action.ClearIssue(); + } + *scheduled.MutableActions(processedActions)->MutableIssue() = std::move(issue); + } + } else { scheduled.ClearActions(); + } } return response.GetStatus().GetCode() == TStatus::ALLOW @@ -701,12 +759,15 @@ bool TCms::TryToLockStateStorageReplica(const TAction& action, case MODE_MAX_AVAILABILITY: if (restartRings + lockedRings > 1) { error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Too many unavailable state storage rings" - << ". Restarting rings: " - << (currentRingState == TStateStorageRingInfo::Restart ? restartRings : restartRings - 1) - << ". Temporary (for a 2 minutes) locked rings: " - << (currentRingState == TStateStorageRingInfo::Locked ? lockedRings + 1 : lockedRings) - << ". Maximum allowed number of unavailable rings for this mode: " << 1; + error.Reason = TReason( + TStringBuilder() << "Too many unavailable state storage rings" + << ". Restarting rings: " + << (currentRingState == TStateStorageRingInfo::Restart ? restartRings : restartRings - 1) + << ". Temporary (for a 2 minutes) locked rings: " + << (currentRingState == TStateStorageRingInfo::Locked ? lockedRings + 1 : lockedRings) + << ". Maximum allowed number of unavailable rings for this mode: " << 1, + TReason::EType::TooManyUnavailableStateStorageRings + ); error.Deadline = defaultDeadline; return false; } @@ -714,13 +775,16 @@ bool TCms::TryToLockStateStorageReplica(const TAction& action, case MODE_KEEP_AVAILABLE: if (restartRings + lockedRings + disabledRings > (nToSelect - 1) / 2) { error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Too many unavailable state storage rings" - << ". Restarting rings: " - << (currentRingState == TStateStorageRingInfo::Restart ? restartRings : restartRings - 1) - << ". Temporary (for a 2 minutes) locked rings: " - << (currentRingState == TStateStorageRingInfo::Locked ? lockedRings + 1 : lockedRings) - << ". Disabled rings: " << disabledRings - << ". Maximum allowed number of unavailable rings for this mode: " << (nToSelect - 1) / 2; + error.Reason = TReason( + TStringBuilder() << "Too many unavailable state storage rings" + << ". Restarting rings: " + << (currentRingState == TStateStorageRingInfo::Restart ? restartRings : restartRings - 1) + << ". Temporary (for a 2 minutes) locked rings: " + << (currentRingState == TStateStorageRingInfo::Locked ? lockedRings + 1 : lockedRings) + << ". Disabled rings: " << disabledRings + << ". Maximum allowed number of unavailable rings for this mode: " << (nToSelect - 1) / 2, + TReason::EType::TooManyUnavailableStateStorageRings + ); error.Deadline = defaultDeadline; return false; } @@ -1484,6 +1548,13 @@ void TCms::CheckAndEnqueueRequest(TEvCms::TEvPermissionRequest::TPtr &ev, const ev, TStatus::WRONG_REQUEST, "Priority value is out of range", ctx); } + for (const auto &action : rec.GetActions()) { + if (action.HasIssue()) { + return ReplyWithError( + ev, TStatus::WRONG_REQUEST, TStringBuilder() << "Action issue is read-only", ctx); + } + } + EnqueueRequest(ev.Release(), ctx); } diff --git a/ydb/core/cms/cms_maintenance_api_ut.cpp b/ydb/core/cms/cms_maintenance_api_ut.cpp index a1f49f8616b8..151461525408 100644 --- a/ydb/core/cms/cms_maintenance_api_ut.cpp +++ b/ydb/core/cms/cms_maintenance_api_ut.cpp @@ -69,6 +69,32 @@ Y_UNIT_TEST_SUITE(TMaintenanceApiTest) { ) ); } + + Y_UNIT_TEST(ActionReason) { + TCmsTestEnv env(8); + + auto response = env.CheckMaintenanceTaskCreate("task-1", Ydb::StatusIds::SUCCESS, + MakeActionGroup( + MakeLockAction(env.GetNodeId(0), TDuration::Minutes(10)) + ), + MakeActionGroup( + MakeLockAction(env.GetNodeId(1), TDuration::Minutes(10)) + ) + ); + + UNIT_ASSERT_VALUES_EQUAL(response.action_group_states().size(), 2); + UNIT_ASSERT_VALUES_EQUAL(response.action_group_states(0).action_states().size(), 1); + const auto &a1 = response.action_group_states(0).action_states(0); + UNIT_ASSERT_VALUES_EQUAL(a1.status(), ActionState::ACTION_STATUS_PERFORMED); + UNIT_ASSERT_VALUES_EQUAL(a1.reason(), ActionState::ACTION_REASON_OK); + UNIT_ASSERT(a1.reason_details().empty()); + + UNIT_ASSERT_VALUES_EQUAL(response.action_group_states(1).action_states().size(), 1); + const auto &a2 = response.action_group_states(1).action_states(0); + UNIT_ASSERT_VALUES_EQUAL(a2.status(), ActionState::ACTION_STATUS_PENDING); + UNIT_ASSERT_VALUES_EQUAL(a2.reason(), ActionState::ACTION_REASON_TOO_MANY_UNAVAILABLE_VDISKS); + UNIT_ASSERT(a2.reason_details().Contains("too many unavailable vdisks")); + } } } // namespace NKikimr::NCmsTest diff --git a/ydb/core/cms/cms_ut.cpp b/ydb/core/cms/cms_ut.cpp index e6e520981307..81c5914ffa59 100644 --- a/ydb/core/cms/cms_ut.cpp +++ b/ydb/core/cms/cms_ut.cpp @@ -631,6 +631,104 @@ Y_UNIT_TEST_SUITE(TCmsTest) { env.CheckListRequests("user1", 0); } + Y_UNIT_TEST(ActionIssue) + { + TCmsTestEnv env(16); + + // Acquire lock on one node + auto rec = env.CheckPermissionRequest + ("user", false, false, true, true, TStatus::ALLOW, + MakeAction(TAction::SHUTDOWN_HOST, env.GetNodeId(0), 60000000)); + UNIT_ASSERT_VALUES_EQUAL(rec.PermissionsSize(), 1); + UNIT_ASSERT(!rec.GetPermissions(0).GetAction().HasIssue()); + + auto pid = rec.GetPermissions(0).GetId(); + + // Schedule request + rec = env.CheckPermissionRequest + ("user", false, false, true, true, TStatus::DISALLOW_TEMP, + MakeAction(TAction::SHUTDOWN_HOST, env.GetNodeId(9), 60000000), + MakeAction(TAction::SHUTDOWN_HOST, env.GetNodeId(1), 60000000)); + UNIT_ASSERT_VALUES_EQUAL(rec.PermissionsSize(), 0); + + auto rid = rec.GetRequestId(); + + // Get scheduled request + auto scheduledRec = env.CheckGetRequest("user", rid); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.RequestsSize(), 1); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.GetRequests(0).ActionsSize(), 2); + auto action1 = scheduledRec.GetRequests(0).GetActions(0); + UNIT_ASSERT(!action1.HasIssue()); + auto action2 = scheduledRec.GetRequests(0).GetActions(1); + UNIT_ASSERT(action2.HasIssue()); + UNIT_ASSERT_VALUES_EQUAL(action2.GetIssue().GetType(), TAction::TIssue::TOO_MANY_UNAVAILABLE_VDISKS); + + // Try to check request + env.CheckRequest("user", rid, false, TStatus::DISALLOW_TEMP); + + // Get scheduled request + scheduledRec = env.CheckGetRequest("user", rid); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.RequestsSize(), 1); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.GetRequests(0).ActionsSize(), 2); + action1 = scheduledRec.GetRequests(0).GetActions(0); + UNIT_ASSERT(!action1.HasIssue()); + action2 = scheduledRec.GetRequests(0).GetActions(1); + UNIT_ASSERT(action2.HasIssue()); + UNIT_ASSERT_VALUES_EQUAL(action2.GetIssue().GetType(), TAction::TIssue::TOO_MANY_UNAVAILABLE_VDISKS); + + // Done with permission + env.CheckDonePermission("user", pid); + + // Try to check request + rec = env.CheckRequest("user", rid, false, TStatus::ALLOW, 2); + UNIT_ASSERT(!rec.GetPermissions(0).GetAction().HasIssue()); + UNIT_ASSERT(!rec.GetPermissions(1).GetAction().HasIssue()); + + env.CheckGetRequest("user", rid, false, TStatus::WRONG_REQUEST); + } + + Y_UNIT_TEST(ActionIssuePartialPermissions) + { + TCmsTestEnv env(8); + + // Schedule request + auto rec = env.CheckPermissionRequest + ("user", true, false, true, true, TStatus::ALLOW_PARTIAL, + MakeAction(TAction::SHUTDOWN_HOST, env.GetNodeId(0), 60000000), + MakeAction(TAction::SHUTDOWN_HOST, env.GetNodeId(1), 60000000)); + UNIT_ASSERT_VALUES_EQUAL(rec.PermissionsSize(), 1); + UNIT_ASSERT(!rec.GetPermissions(0).GetAction().HasIssue()); + + auto pid = rec.GetPermissions(0).GetId(); + auto rid = rec.GetRequestId(); + + // Get scheduled request + auto scheduledRec = env.CheckGetRequest("user", rid); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.RequestsSize(), 1); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.GetRequests(0).ActionsSize(), 1); + auto action = scheduledRec.GetRequests(0).GetActions(0); + UNIT_ASSERT_VALUES_EQUAL(action.GetIssue().GetType(), TAction::TIssue::TOO_MANY_UNAVAILABLE_VDISKS); + + // Try to check request + env.CheckRequest("user", rid, false, TStatus::DISALLOW_TEMP); + + // Get scheduled request + scheduledRec = env.CheckGetRequest("user", rid); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.RequestsSize(), 1); + UNIT_ASSERT_VALUES_EQUAL(scheduledRec.GetRequests(0).ActionsSize(), 1); + action = scheduledRec.GetRequests(0).GetActions(0); + UNIT_ASSERT_VALUES_EQUAL(action.GetIssue().GetType(), TAction::TIssue::TOO_MANY_UNAVAILABLE_VDISKS); + + // Done with permission + env.CheckDonePermission("user", pid); + + // Try to check request + rec = env.CheckRequest("user", rid, false, TStatus::ALLOW, 1); + UNIT_ASSERT(!rec.GetPermissions(0).GetAction().HasIssue()); + + env.CheckGetRequest("user", rid, false, TStatus::WRONG_REQUEST); + } + Y_UNIT_TEST(WalleTasks) { TCmsTestEnv env(24, 4); @@ -1808,6 +1906,51 @@ Y_UNIT_TEST_SUITE(TCmsTest) { env.CheckRejectRequest("user", request3.GetRequestId()); } + Y_UNIT_TEST(AllVDisksEvictionInRack) + { + auto opts = TTestEnvOpts(8) + .WithSentinel() + .WithNodeLocationCallback([](ui32 nodeId) { + NActorsInterconnect::TNodeLocation location; + location.SetRack(ToString(nodeId / 2 + 1)); + return TNodeLocation(location); // Node = [0, 1, 2, 3, 4, 5, 6, 7] + // Rack = [1, 1, 2, 2, 3, 3, 4, 4] + }); + TCmsTestEnv env(opts); + env.SetLogPriority(NKikimrServices::CMS, NLog::PRI_DEBUG); + + // Evict all VDisks from rack 1 + auto request1 = env.CheckPermissionRequest( + MakePermissionRequest(TRequestOptions("user").WithEvictVDisks(), + MakeAction(TAction::RESTART_SERVICES, env.GetNodeId(0), 600000000, "storage") + ), + TStatus::DISALLOW_TEMP // ok, waiting for move VDisks + ); + auto request2 = env.CheckPermissionRequest( + MakePermissionRequest(TRequestOptions("user").WithEvictVDisks(), + MakeAction(TAction::RESTART_SERVICES, env.GetNodeId(1), 600000000, "storage") + ), + TStatus::DISALLOW_TEMP // ok, waiting for move VDisks + ); + + // Check that FAULTY BSC requests are sent + env.CheckBSCUpdateRequests({ env.GetNodeId(0), env.GetNodeId(1) }, NKikimrBlobStorage::FAULTY); + + // "Move" VDisks from rack 1 + auto& node1 = TFakeNodeWhiteboardService::Info[env.GetNodeId(0)]; + node1.VDisksMoved = true; + node1.VDiskStateInfo.clear(); + auto& node2 = TFakeNodeWhiteboardService::Info[env.GetNodeId(1)]; + node2.VDisksMoved = true; + node2.VDiskStateInfo.clear(); + env.RegenerateBSConfig(TFakeNodeWhiteboardService::Config.MutableResponse()->MutableStatus(0)->MutableBaseConfig(), opts); + + auto permission1 = env.CheckRequest("user", request1.GetRequestId(), false, TStatus::ALLOW, 1); + auto permission2 = env.CheckRequest("user", request2.GetRequestId(), false, TStatus::ALLOW, 1); + env.CheckDonePermission("user", permission1.GetPermissions(0).GetId()); + env.CheckDonePermission("user", permission2.GetPermissions(0).GetId()); + } + Y_UNIT_TEST(EmergencyDuringRollingRestart) { TCmsTestEnv env(8); diff --git a/ydb/core/cms/cms_ut_common.cpp b/ydb/core/cms/cms_ut_common.cpp index b57c54dcc01c..4a0f6715e431 100644 --- a/ydb/core/cms/cms_ut_common.cpp +++ b/ydb/core/cms/cms_ut_common.cpp @@ -29,6 +29,17 @@ const bool ENABLE_DETAILED_CMS_LOG = true; const bool ENABLE_DETAILED_CMS_LOG = false; #endif +#define COMMA , +Y_DECLARE_OUT_SPEC(, std::map>, o, value) { + std::vector pairs; + for (const auto& [status, nodes] : value) { + pairs.push_back( + TStringBuilder() << status << "=" << '[' << JoinSeq(',', nodes) << ']' + ); + } + o << '[' << JoinSeq(',', pairs) << ']'; +}; + namespace NKikimr { namespace NCmsTest { @@ -391,7 +402,7 @@ static NKikimrConfig::TBootstrap GenerateBootstrapConfig(TTestActorRuntime &runt return res; } -static void SetupServices(TTestActorRuntime &runtime, const TTestEnvOpts &options) { +static void SetupServices(TTestBasicRuntime &runtime, const TTestEnvOpts &options) { const ui32 domainsNum = 1; const ui32 disksInDomain = 1; @@ -503,6 +514,7 @@ static void SetupServices(TTestActorRuntime &runtime, const TTestEnvOpts &option ), 0); + runtime.LocationCallback = options.NodeLocationCallback; runtime.Initialize(app.Unwrap()); auto dnsConfig = new TDynamicNameserviceConfig(); dnsConfig->MaxStaticNodeId = 1000; @@ -868,6 +880,39 @@ TCmsTestEnv::CheckRequest(const TString &user, return rec; } +void TCmsTestEnv::CheckBSCUpdateRequests(std::set expectedNodes, + NKikimrBlobStorage::EDriveStatus expectedStatus) +{ + using TBSCRequests = std::map>; + + TBSCRequests expectedRequests = { {expectedStatus, expectedNodes} }; + TBSCRequests actualRequests; + + TDispatchOptions options; + options.FinalEvents.emplace_back([&](IEventHandle& ev) { + if (ev.GetTypeRewrite() == TEvBlobStorage::TEvControllerConfigRequest::EventType) { + const auto& request = ev.Get()->Record; + bool foundUpdateDriveCommand = false; + for (const auto& command : request.GetRequest().GetCommand()) { + if (command.HasUpdateDriveStatus()) { + foundUpdateDriveCommand = true; + const auto& update = command.GetUpdateDriveStatus(); + actualRequests[update.GetStatus()].insert(update.GetHostKey().GetNodeId()); + } + } + return foundUpdateDriveCommand; + } + return false; + }); + DispatchEvents(options, TDuration::Minutes(1)); + + UNIT_ASSERT_C( + actualRequests == expectedRequests, + TStringBuilder() << "Sentinel sent wrong update requests to BSC: " + << "expected# " << expectedRequests + << ", actual# " << actualRequests + ); +} void TCmsTestEnv::CheckWalleStoreTaskIsFailed(NCms::TEvCms::TEvStoreWalleTask* req) { diff --git a/ydb/core/cms/cms_ut_common.h b/ydb/core/cms/cms_ut_common.h index 43dd71c4b61d..c719133702f5 100644 --- a/ydb/core/cms/cms_ut_common.h +++ b/ydb/core/cms/cms_ut_common.h @@ -92,6 +92,9 @@ struct TTestEnvOpts { bool EnableCMSRequestPriorities; bool EnableSingleCompositeActionGroup; + using TNodeLocationCallback = std::function; + TNodeLocationCallback NodeLocationCallback; + TTestEnvOpts() = default; TTestEnvOpts(ui32 nodeCount, @@ -126,6 +129,12 @@ struct TTestEnvOpts { EnableCMSRequestPriorities = false; return *this; } + + TTestEnvOpts& WithNodeLocationCallback(TNodeLocationCallback nodeLocationCallback) { + NodeLocationCallback = nodeLocationCallback; + return *this; + } + }; class TCmsTestEnv : public TTestBasicRuntime { @@ -323,6 +332,8 @@ class TCmsTestEnv : public TTestBasicRuntime { return CheckRequest(user, id, dry, NKikimrCms::MODE_MAX_AVAILABILITY, res, count); } + void CheckBSCUpdateRequests(std::set expectedNodes, NKikimrBlobStorage::EDriveStatus expectedStatus); + void CheckWalleStoreTaskIsFailed(NCms::TEvCms::TEvStoreWalleTask *req); template diff --git a/ydb/core/cms/console/configs_dispatcher.cpp b/ydb/core/cms/console/configs_dispatcher.cpp index 9688829f0f9b..3c0cc91dcb00 100644 --- a/ydb/core/cms/console/configs_dispatcher.cpp +++ b/ydb/core/cms/console/configs_dispatcher.cpp @@ -927,7 +927,7 @@ void TConfigsDispatcher::Handle(TEvConsole::TEvConfigSubscriptionNotification::T if (subscription->Yaml && YamlConfigEnabled) { ReplaceConfigItems(YamlProtoConfig, trunc, FilterKinds(subscription->Kinds), BaseConfig); } else { - Y_FOR_EACH_BIT(kind, kinds) { + Y_FOR_EACH_BIT(kind, FilterKinds(kinds)) { if (affectedKinds.contains(kind)) { hasAffectedKinds = true; } @@ -941,15 +941,15 @@ void TConfigsDispatcher::Handle(TEvConsole::TEvConfigSubscriptionNotification::T ReplaceConfigItems(ev->Get()->Record.GetConfig(), trunc, FilterKinds(kinds), BaseConfig); } - if (hasAffectedKinds || !CompareConfigs(subscription->CurrentConfig.Config, trunc) || CurrentStateFunc() == &TThis::StateInit) { + if (hasAffectedKinds || !CompareConfigs(subscription->CurrentConfig.Config, trunc, FilterKinds(kinds)) || CurrentStateFunc() == &TThis::StateInit) { subscription->UpdateInProcess = MakeHolder(); subscription->UpdateInProcess->Record.MutableConfig()->CopyFrom(trunc); subscription->UpdateInProcess->Record.SetLocal(true); - Y_FOR_EACH_BIT(kind, kinds) { + Y_FOR_EACH_BIT(kind, FilterKinds(kinds)) { subscription->UpdateInProcess->Record.AddItemKinds(kind); } subscription->UpdateInProcessCookie = ++NextRequestCookie; - subscription->UpdateInProcessConfigVersion = FilterVersion(ev->Get()->Record.GetConfig().GetVersion(), kinds); + subscription->UpdateInProcessConfigVersion = FilterVersion(ev->Get()->Record.GetConfig().GetVersion(), FilterKinds(kinds)); if (YamlConfigEnabled) { UpdateYamlVersion(subscription); @@ -1046,6 +1046,12 @@ void TConfigsDispatcher::Handle(TEvConfigsDispatcher::TEvSetConfigSubscriptionRe "SetConfigSubscriptionRequest handler"); Y_UNUSED(nonYamlKinds); auto kinds = KindsToBitMap(ev->Get()->ConfigItemKinds); + + auto truncKinds = FilterKinds(kinds); + if (truncKinds.Empty() && !kinds.Empty()) { + return; + } + auto subscriberActor = ev->Get()->Subscriber ? ev->Get()->Subscriber : ev->Sender; auto subscription = FindSubscription(kinds); diff --git a/ydb/core/cms/erasure_checkers.cpp b/ydb/core/cms/erasure_checkers.cpp index 8d1b257ec1a2..1d753ba32cf0 100644 --- a/ydb/core/cms/erasure_checkers.cpp +++ b/ydb/core/cms/erasure_checkers.cpp @@ -77,10 +77,13 @@ bool TErasureCounterBase::CheckForMaxAvailability(TClusterInfoPtr info, TErrorIn } error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" + error.Reason = TReason( + TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" << ": too many unavailable vdisks" << ". Locked: " << DumpVDisksInfo(Locked, info) - << ". Down: " << DumpVDisksInfo(Down, info); + << ". Down: " << DumpVDisksInfo(Down, info), + TReason::EType::TooManyUnavailableVDisks + ); error.Deadline = defaultDeadline; return false; } @@ -150,10 +153,13 @@ bool TDefaultErasureCounter::CheckForKeepAvailability(TClusterInfoPtr info, TErr } error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" + error.Reason = TReason( + TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" << ": too many unavailable vdisks" << ". Locked: " << DumpVDisksInfo(Locked, info) - << ". Down: " << DumpVDisksInfo(Down, info); + << ". Down: " << DumpVDisksInfo(Down, info), + TReason::EType::TooManyUnavailableVDisks + ); error.Deadline = defaultDeadline; return false; } @@ -191,20 +197,26 @@ bool TMirror3dcCounter::CheckForKeepAvailability(TClusterInfoPtr info, TErrorInf if (DataCenterDisabledNodes.size() > 2) { error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" + error.Reason = TReason( + TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" << ": too many unavailable vdisks" << ". Number of data centers with unavailable vdisks: " << DataCenterDisabledNodes.size() << ". Locked: " << DumpVDisksInfo(Locked, info) - << ". Down: " << DumpVDisksInfo(Down, info); + << ". Down: " << DumpVDisksInfo(Down, info), + TReason::EType::TooManyUnavailableVDisks + ); error.Deadline = defaultDeadline; return false; } error.Code = TStatus::DISALLOW_TEMP; - error.Reason = TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" + error.Reason = TReason( + TStringBuilder() << "Issue in affected group with id '" << GroupId << "'" << ": too many unavailable vdisks" << ". Locked: " << DumpVDisksInfo(Locked, info) - << ". Down: " << DumpVDisksInfo(Down, info); + << ". Down: " << DumpVDisksInfo(Down, info), + TReason::EType::TooManyUnavailableVDisks + ); error.Deadline = defaultDeadline; return false; diff --git a/ydb/core/cms/error_info.h b/ydb/core/cms/error_info.h new file mode 100644 index 000000000000..c6346f133330 --- /dev/null +++ b/ydb/core/cms/error_info.h @@ -0,0 +1,59 @@ +#pragma once + +#include "defs.h" + +namespace NKikimr::NCms { + +class TReason { +public: + // Must be sync with proto enum + enum class EType { + Generic, + TooManyUnavailableVDisks, + TooManyUnavailableStateStorageRings, + DisabledNodesLimitReached, + TenantDisabledNodesLimitReached, + SysTabletsNodeLimitReached, + }; + + TReason(const TString &message, EType type = EType::Generic) + : Message(message) + , Type(type) + {} + + TReason(const char* message, EType type = EType::Generic) + : Message(message) + , Type(type) + {} + + TReason() = default; + + operator TString() const { + return Message; + } + + const TString& GetMessage() const { + return Message; + } + + EType GetType() const { + return Type; + } + +private: + TString Message; + EType Type = EType::Generic; +}; + +struct TErrorInfo { + NKikimrCms::TStatus::ECode Code = NKikimrCms::TStatus::ALLOW; + TReason Reason; + TInstant Deadline; + ui64 RollbackPoint = 0; +}; + +} // namespace NKikimr::NCms + +Y_DECLARE_OUT_SPEC(inline, NKikimr::NCms::TReason, stream, value) { + stream << value.GetMessage(); +} diff --git a/ydb/core/cms/json_proxy_proto.h b/ydb/core/cms/json_proxy_proto.h index 8079eb6971e2..d65d419f4495 100644 --- a/ydb/core/cms/json_proxy_proto.h +++ b/ydb/core/cms/json_proxy_proto.h @@ -80,6 +80,8 @@ class TJsonProxyProto : public TActorBootstrapped { return ReplyWithTypeDescription(*NKikimrConfig::TImmediateControlsConfig::TVDiskControls::descriptor(), ctx); else if (name == ".NKikimrConfig.TImmediateControlsConfig.TTabletControls") return ReplyWithTypeDescription(*NKikimrConfig::TImmediateControlsConfig::TTabletControls::descriptor(), ctx); + else if (name == ".NKikimrConfig.TImmediateControlsConfig.TBlobStorageControllerControls") + return ReplyWithTypeDescription(*NKikimrConfig::TImmediateControlsConfig::TBlobStorageControllerControls::descriptor(), ctx); } ctx.Send(RequestEvent->Sender, diff --git a/ydb/core/cms/node_checkers.cpp b/ydb/core/cms/node_checkers.cpp index 51306ab3efb6..b478063a6d98 100644 --- a/ydb/core/cms/node_checkers.cpp +++ b/ydb/core/cms/node_checkers.cpp @@ -87,7 +87,7 @@ const THashMap& TNodesCounterBase::GetNodeToSta return NodeToState; } -bool TNodesLimitsCounterBase::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TString& reason) const { +bool TNodesLimitsCounterBase::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TReason& reason) const { Y_ABORT_UNLESS(NodeToState.contains(nodeId)); auto nodeState = NodeToState.at(nodeId); @@ -126,28 +126,34 @@ bool TNodesLimitsCounterBase::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabili const auto disabledNodes = LockedNodesCount + DownNodesCount + 1; if (DisabledNodesLimit > 0 && disabledNodes > DisabledNodesLimit) { - reason = TStringBuilder() << ReasonPrefix(nodeId) + reason = TReason( + TStringBuilder() << ReasonPrefix(nodeId) << ": too many unavailable nodes." << " Locked: " << LockedNodesCount << ", down: " << DownNodesCount - << ", limit: " << DisabledNodesLimit; + << ", limit: " << DisabledNodesLimit, + DisabledNodesLimitReachedReasonType() + ); return false; } if (DisabledNodesRatioLimit > 0 && (disabledNodes * 100 > NodeToState.size() * DisabledNodesRatioLimit)) { - reason = TStringBuilder() << ReasonPrefix(nodeId) + reason = TReason( + TStringBuilder() << ReasonPrefix(nodeId) << ": too many unavailable nodes." << " Locked: " << LockedNodesCount << ", down: " << DownNodesCount << ", total: " << NodeToState.size() - << ", limit: " << DisabledNodesRatioLimit << "%"; + << ", limit: " << DisabledNodesRatioLimit << "%", + DisabledNodesLimitReachedReasonType() + ); return false; } return true; } -bool TSysTabletsNodesCounter::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TString& reason) const { +bool TSysTabletsNodesCounter::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TReason& reason) const { Y_ABORT_UNLESS(NodeToState.contains(nodeId)); auto nodeState = NodeToState.at(nodeId); @@ -198,12 +204,15 @@ bool TSysTabletsNodesCounter::TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabili Y_ABORT("Unknown availability mode"); } - reason = TStringBuilder() << "Cannot lock node '" << nodeId << "'" + reason = TReason( + TStringBuilder() << "Cannot lock node '" << nodeId << "'" << ": tablet '" << NKikimrConfig::TBootstrap_ETabletType_Name(TabletType) << "'" << " has too many unavailable nodes." << " Locked: " << LockedNodesCount << ", down: " << DownNodesCount - << ", limit: " << limit; + << ", limit: " << limit, + TReason::EType::SysTabletsNodeLimitReached + ); return false; } diff --git a/ydb/core/cms/node_checkers.h b/ydb/core/cms/node_checkers.h index a71066cfe2df..55b606e5ce58 100644 --- a/ydb/core/cms/node_checkers.h +++ b/ydb/core/cms/node_checkers.h @@ -1,6 +1,7 @@ #pragma once #include "defs.h" +#include "error_info.h" #include #include @@ -39,7 +40,7 @@ class INodesChecker { virtual void LockNode(ui32 nodeId) = 0; virtual void UnlockNode(ui32 nodeId) = 0; - virtual bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TString& reason) const = 0; + virtual bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TReason& reason) const = 0; }; /** @@ -80,7 +81,13 @@ class TNodesLimitsCounterBase : public TNodesCounterBase { ui32 DisabledNodesLimit; ui32 DisabledNodesRatioLimit; - virtual TString ReasonPrefix(ui32 nodeId) const = 0; + virtual TString ReasonPrefix(ui32 nodeId) const { + return TStringBuilder() << "Cannot lock node '" << nodeId << "'"; + } + + virtual TReason::EType DisabledNodesLimitReachedReasonType() const { + return TReason::EType::DisabledNodesLimitReached; + }; public: explicit TNodesLimitsCounterBase(ui32 disabledNodesLimit, ui32 disabledNodesRatioLimit) @@ -94,7 +101,7 @@ class TNodesLimitsCounterBase : public TNodesCounterBase { DisabledNodesRatioLimit = ratioLimit; } - bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TString& reason) const override final; + bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TReason& reason) const override final; }; class TTenantLimitsCounter : public TNodesLimitsCounterBase { @@ -106,6 +113,10 @@ class TTenantLimitsCounter : public TNodesLimitsCounterBase { return TStringBuilder() << "Cannot lock node '" << nodeId << "' of tenant '" << TenantName << "'"; } + TReason::EType DisabledNodesLimitReachedReasonType() const override final { + return TReason::EType::TenantDisabledNodesLimitReached; + } + public: explicit TTenantLimitsCounter(const TString& tenantName, ui32 disabledNodesLimit, ui32 disabledNodesRatioLimit) : TNodesLimitsCounterBase(disabledNodesLimit, disabledNodesRatioLimit) @@ -115,11 +126,6 @@ class TTenantLimitsCounter : public TNodesLimitsCounterBase { }; class TClusterLimitsCounter : public TNodesLimitsCounterBase { -protected: - TString ReasonPrefix(ui32 nodeId) const override final { - return TStringBuilder() << "Cannot lock node '" << nodeId << "'"; - } - public: explicit TClusterLimitsCounter(ui32 disabledNodesLimit, ui32 disabledNodesRatioLimit) : TNodesLimitsCounterBase(disabledNodesLimit, disabledNodesRatioLimit) @@ -143,7 +149,7 @@ class TSysTabletsNodesCounter : public TNodesCounterBase { { } - bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TString& reason) const override final; + bool TryToLockNode(ui32 nodeId, NKikimrCms::EAvailabilityMode mode, TReason& reason) const override final; }; } // namespace NKikimr::NCms diff --git a/ydb/core/cms/pdisk_status.h b/ydb/core/cms/pdisk_status.h new file mode 100644 index 000000000000..b36a86cec46c --- /dev/null +++ b/ydb/core/cms/pdisk_status.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace NKikimr::NCms { + +using EPDiskStatus = NKikimrBlobStorage::EDriveStatus; + +} // namespace NKikimr::NCms diff --git a/ydb/core/cms/sentinel.cpp b/ydb/core/cms/sentinel.cpp index 3ab0d3cf883d..cd06c2f65003 100644 --- a/ydb/core/cms/sentinel.cpp +++ b/ydb/core/cms/sentinel.cpp @@ -125,6 +125,10 @@ void TPDiskStatusComputer::SetForcedStatus(EPDiskStatus status) { ForcedStatus = status; } +bool TPDiskStatusComputer::HasForcedStatus() const { + return ForcedStatus.Defined(); +} + void TPDiskStatusComputer::ResetForcedStatus() { ForcedStatus.Clear(); } @@ -196,6 +200,7 @@ void TPDiskStatus::DisallowChanging() { TPDiskInfo::TPDiskInfo(EPDiskStatus initialStatus, const ui32& defaultStateLimit, const TLimitsMap& stateLimits) : TPDiskStatus(initialStatus, defaultStateLimit, stateLimits) + , ActualStatus(initialStatus) { Touch(); } @@ -898,7 +903,7 @@ class TSentinel: public TActorBootstrapped { all.AddPDisk(id); if (info.IsChanged()) { - if (info.IsNewStatusGood()) { + if (info.IsNewStatusGood() || info.HasForcedStatus()) { alwaysAllowed.insert(id); } else { changed.AddPDisk(id); diff --git a/ydb/core/cms/sentinel_impl.h b/ydb/core/cms/sentinel_impl.h index 8622050ff1d8..52cc9d8af030 100644 --- a/ydb/core/cms/sentinel_impl.h +++ b/ydb/core/cms/sentinel_impl.h @@ -3,8 +3,7 @@ #include "defs.h" #include "pdiskid.h" #include "pdisk_state.h" - -#include +#include "pdisk_status.h" #include #include @@ -12,7 +11,6 @@ namespace NKikimr::NCms::NSentinel { -using EPDiskStatus = NKikimrBlobStorage::EDriveStatus; using TLimitsMap = TMap; class TPDiskStatusComputer { @@ -29,6 +27,7 @@ class TPDiskStatusComputer { void Reset(); void SetForcedStatus(EPDiskStatus status); + bool HasForcedStatus() const; void ResetForcedStatus(); private: @@ -84,7 +83,7 @@ struct TPDiskInfo using EIgnoreReason = NKikimrCms::TPDiskInfo::EIgnoreReason; EPDiskStatus ActualStatus = EPDiskStatus::ACTIVE; - EPDiskStatus PrevStatus = EPDiskStatus::ACTIVE; + EPDiskStatus PrevStatus = EPDiskStatus::UNKNOWN; TInstant LastStatusChange; bool StatusChangeFailed = false; // means that this pdisk status change last time was the reason of whole request failure diff --git a/ydb/core/cms/ya.make b/ydb/core/cms/ya.make index d3edcc7908e7..1d4c9845dc5f 100644 --- a/ydb/core/cms/ya.make +++ b/ydb/core/cms/ya.make @@ -30,6 +30,7 @@ SRCS( downtime.cpp erasure_checkers.h erasure_checkers.cpp + error_info.h http.cpp http.h info_collector.cpp diff --git a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp index 24a24f0c7787..2fbd98dad89f 100644 --- a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp +++ b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp @@ -243,6 +243,29 @@ #include +#ifndef KIKIMR_DISABLE_S3_OPS +#include +#endif + +namespace { + +#ifndef KIKIMR_DISABLE_S3_OPS +struct TAwsApiGuard { + TAwsApiGuard() { + Aws::InitAPI(Options); + } + + ~TAwsApiGuard() { + Aws::ShutdownAPI(Options); + } + +private: + Aws::SDKOptions Options; +}; +#endif + +} + namespace NKikimr { namespace NKikimrServicesInitializers { @@ -2816,5 +2839,18 @@ void TGraphServiceInitializer::InitializeServices(NActors::TActorSystemSetup* se TActorSetupCmd(NGraph::CreateGraphService(appData->TenantName), TMailboxType::HTSwap, appData->UserPoolId)); } +#ifndef KIKIMR_DISABLE_S3_OPS +TAwsApiInitializer::TAwsApiInitializer(IGlobalObjectStorage& globalObjects) + : GlobalObjects(globalObjects) +{ +} + +void TAwsApiInitializer::InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) { + Y_UNUSED(setup); + Y_UNUSED(appData); + GlobalObjects.AddGlobalObject(std::make_shared()); +} +#endif + } // namespace NKikimrServicesInitializers } // namespace NKikimr diff --git a/ydb/core/driver_lib/run/kikimr_services_initializers.h b/ydb/core/driver_lib/run/kikimr_services_initializers.h index 87d692434081..04f30522186f 100644 --- a/ydb/core/driver_lib/run/kikimr_services_initializers.h +++ b/ydb/core/driver_lib/run/kikimr_services_initializers.h @@ -618,5 +618,16 @@ class TGraphServiceInitializer : public IKikimrServicesInitializer { void InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) override; }; +#ifndef KIKIMR_DISABLE_S3_OPS +class TAwsApiInitializer : public IServiceInitializer { + IGlobalObjectStorage& GlobalObjects; + +public: + TAwsApiInitializer(IGlobalObjectStorage& globalObjects); + + void InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) override; +}; +#endif + } // namespace NKikimrServicesInitializers } // namespace NKikimr diff --git a/ydb/core/driver_lib/run/run.cpp b/ydb/core/driver_lib/run/run.cpp index bc70d1e83bd4..5413287f4b96 100644 --- a/ydb/core/driver_lib/run/run.cpp +++ b/ydb/core/driver_lib/run/run.cpp @@ -495,8 +495,11 @@ static TString ReadFile(const TString& fileName) { } void TKikimrRunner::InitializeGracefulShutdown(const TKikimrRunConfig& runConfig) { - Y_UNUSED(runConfig); GracefulShutdownSupported = true; + const auto& config = runConfig.AppConfig.GetShutdownConfig(); + if (config.HasMinDelayBeforeShutdownSeconds()) { + MinDelayBeforeShutdown = TDuration::Seconds(config.GetMinDelayBeforeShutdownSeconds()); + } } void TKikimrRunner::InitializeKqpController(const TKikimrRunConfig& runConfig) { @@ -1655,6 +1658,12 @@ TIntrusivePtr TKikimrRunner::CreateServiceInitializers sil->AddServiceInitializer(new TGraphServiceInitializer(runConfig)); } +#ifndef KIKIMR_DISABLE_S3_OPS + if (serviceMask.EnableAwsService) { + sil->AddServiceInitializer(new TAwsApiInitializer(*this)); + } +#endif + return sil; } @@ -1702,6 +1711,7 @@ void TKikimrRunner::KikimrStop(bool graceful) { ActorSystem->Send(new IEventHandle(NGRpcService::CreateGrpcPublisherServiceActorId(), {}, new TEvents::TEvPoisonPill)); } + THPTimer timer; TIntrusivePtr drainProgress(new TDrainProgress()); if (AppData->FeatureFlags.GetEnableDrainOnShutdown() && GracefulShutdownSupported && ActorSystem) { drainProgress->OnSend(); @@ -1735,6 +1745,12 @@ void TKikimrRunner::KikimrStop(bool graceful) { } } + // Wait for a minimum delay to make sure that clients forget about this node + auto passedTime = TDuration::Seconds(timer.Passed()); + if (MinDelayBeforeShutdown > passedTime) { + Sleep(MinDelayBeforeShutdown - passedTime); + } + if (ActorSystem) { ActorSystem->BroadcastToProxies([](const TActorId& proxyId) { return new IEventHandle(proxyId, {}, new TEvInterconnect::TEvTerminate); diff --git a/ydb/core/driver_lib/run/run.h b/ydb/core/driver_lib/run/run.h index a6c9283fc43d..eaa4ff1f91a6 100644 --- a/ydb/core/driver_lib/run/run.h +++ b/ydb/core/driver_lib/run/run.h @@ -42,6 +42,7 @@ class TKikimrRunner : public virtual TThrRefBase, private IGlobalObjectStorage { bool EnabledGrpcService = false; bool GracefulShutdownSupported = false; + TDuration MinDelayBeforeShutdown; THolder SqsHttp; THolder YdbDriver; diff --git a/ydb/core/driver_lib/run/service_mask.h b/ydb/core/driver_lib/run/service_mask.h index 3b694ce5ac84..044557229c6b 100644 --- a/ydb/core/driver_lib/run/service_mask.h +++ b/ydb/core/driver_lib/run/service_mask.h @@ -79,6 +79,7 @@ union TBasicKikimrServicesMask { bool EnableGraphService:1; bool EnableCompDiskLimiter:1; bool EnableGroupedMemoryLimiter:1; + bool EnableAwsService:1; }; struct { diff --git a/ydb/core/driver_lib/run/ya.make b/ydb/core/driver_lib/run/ya.make index 987bf4772020..7d7ea53e49e1 100644 --- a/ydb/core/driver_lib/run/ya.make +++ b/ydb/core/driver_lib/run/ya.make @@ -1,5 +1,15 @@ LIBRARY(run) +IF (OS_WINDOWS) + CFLAGS( + -DKIKIMR_DISABLE_S3_OPS + ) +ELSE() + PEERDIR( + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core + ) +ENDIF() + SRCS( auto_config_initializer.cpp config.cpp diff --git a/ydb/core/grpc_services/query/rpc_execute_script.cpp b/ydb/core/grpc_services/query/rpc_execute_script.cpp index 1c5efad67097..7f1502b35574 100644 --- a/ydb/core/grpc_services/query/rpc_execute_script.cpp +++ b/ydb/core/grpc_services/query/rpc_execute_script.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -71,27 +72,28 @@ std::tuple FillKqpRequest( return {Ydb::StatusIds::SUCCESS, {}}; } -class TExecuteScriptRPC : public TActorBootstrapped { +class TExecuteScriptRPC : public TRpcRequestActor { public: + using TRpcRequestActorBase = TRpcRequestActor; + static constexpr NKikimrServices::TActivity::EType ActorActivityType() { return NKikimrServices::TActivity::GRPC_REQ; } - TExecuteScriptRPC(TEvExecuteScriptRequest* request) - : Request_(request) + TExecuteScriptRPC(IRequestNoOpCtx* request) + : TRpcRequestActorBase(request) {} void Bootstrap() { NYql::TIssues issues; - const auto& request = *Request_->GetProtoRequest(); + const auto& request = GetProtoRequest(); - if (request.operation_params().operation_mode() == Ydb::Operations::OperationParams::SYNC) { + if (request->operation_params().operation_mode() == Ydb::Operations::OperationParams::SYNC) { issues.AddIssue("ExecuteScript must be asyncronous operation"); return Reply(Ydb::StatusIds::BAD_REQUEST, issues); } - AuditContextAppend(Request_.get(), request); - + AuditContextAppend(Request.Get(), request); Ydb::StatusIds::StatusCode status = Ydb::StatusIds::SUCCESS; if (auto scriptRequest = MakeScriptRequest(issues, status)) { if (Send(NKqp::MakeKqpProxyID(SelfId().NodeId()), scriptRequest.Release())) { @@ -122,14 +124,14 @@ class TExecuteScriptRPC : public TActorBootstrapped { } THolder MakeScriptRequest(NYql::TIssues& issues, Ydb::StatusIds::StatusCode& status) const { - const auto* req = Request_->GetProtoRequest(); - const auto traceId = Request_->GetTraceId(); + const auto* req = GetProtoRequest(); + const auto traceId = Request->GetTraceId(); auto ev = MakeHolder(); - SetAuthToken(ev, *Request_); - SetDatabase(ev, *Request_); - SetRlPath(ev, *Request_); + SetAuthToken(ev, *Request); + SetDatabase(ev, *Request); + SetRlPath(ev, *Request); if (traceId) { ev->Record.SetTraceId(traceId.GetRef()); @@ -162,12 +164,9 @@ class TExecuteScriptRPC : public TActorBootstrapped { result.set_status(status); - AuditContextAppend(Request_.get(), *Request_->GetProtoRequest(), result); - - TString serializedResult; - Y_PROTOBUF_SUPPRESS_NODISCARD result.SerializeToString(&serializedResult); + AuditContextAppend(Request.Get(), GetProtoRequest(), result); - Request_->SendSerializedResult(std::move(serializedResult), status); + TProtoResponseHelper::SendProtoResponse(result, status, Request); PassAway(); } @@ -177,9 +176,6 @@ class TExecuteScriptRPC : public TActorBootstrapped { result.set_ready(true); Reply(status, std::move(result), issues); } - -private: - std::unique_ptr Request_; }; } // namespace @@ -193,6 +189,11 @@ void DoExecuteScript(std::unique_ptr p, const IFacilityProvider f.RegisterActor(new TExecuteScriptRPC(req)); } +} // namespace NQuery + +template<> +IActor* TEvExecuteScriptRequest::CreateRpcActor(IRequestNoOpCtx* msg) { + return new TExecuteScriptRPC(msg); } } // namespace NKikimr::NGRpcService diff --git a/ydb/core/grpc_services/query/rpc_fetch_script_results.cpp b/ydb/core/grpc_services/query/rpc_fetch_script_results.cpp index e25877095cc3..80d815723b8a 100644 --- a/ydb/core/grpc_services/query/rpc_fetch_script_results.cpp +++ b/ydb/core/grpc_services/query/rpc_fetch_script_results.cpp @@ -35,7 +35,7 @@ class TFetchScriptResultsRPC : public TRpcRequestActorSendSerializedResult(std::move(serializedResult), status); + TProtoResponseHelper::SendProtoResponse(result, status, Request); PassAway(); } @@ -154,4 +151,9 @@ void DoFetchScriptResults(std::unique_ptr p, const IFacilityPro } +template<> +IActor* TEvFetchScriptResultsRequest::CreateRpcActor(IRequestNoOpCtx* msg) { + return new TFetchScriptResultsRPC(msg); +} + } // namespace NKikimr::NGRpcService diff --git a/ydb/core/health_check/health_check.cpp b/ydb/core/health_check/health_check.cpp index 05a26e6285b6..341325b3a313 100644 --- a/ydb/core/health_check/health_check.cpp +++ b/ydb/core/health_check/health_check.cpp @@ -32,6 +32,9 @@ #include #include +#include +#include + static decltype(auto) make_vslot_tuple(const NKikimrBlobStorage::TVSlotId& id) { return std::make_tuple(id.GetNodeId(), id.GetPDiskId(), id.GetVSlotId()); } @@ -61,12 +64,16 @@ struct hash { namespace NKikimr { using NNodeWhiteboard::TNodeId; -using NNodeWhiteboard::TTabletId; namespace NHealthCheck { using namespace NActors; using namespace Ydb; +using namespace NSchemeCache; +using namespace NSchemeShard; +using namespace NSysView; +using namespace NConsole; +using NNodeWhiteboard::TTabletId; void RemoveUnrequestedEntries(Ydb::Monitoring::SelfCheckResult& result, const Ydb::Monitoring::SelfCheckRequest& request) { if (!request.return_verbose_status()) { @@ -118,11 +125,13 @@ class TSelfCheckRequest : public TActorBootstrapped { TActorId Sender; THolder Request; ui64 Cookie; + NWilson::TSpan Span; - TSelfCheckRequest(const TActorId& sender, THolder request, ui64 cookie) + TSelfCheckRequest(const TActorId& sender, THolder request, ui64 cookie, NWilson::TTraceId&& traceId) : Sender(sender) , Request(std::move(request)) , Cookie(cookie) + , Span(TComponentTracingLevels::TTablet::Basic, std::move(traceId), "health_check", NWilson::EFlags::AUTO_END) {} using TGroupId = ui32; @@ -439,6 +448,167 @@ class TSelfCheckRequest : public TActorBootstrapped { } }; + template + struct TRequestResponse { + std::variant, TString> Response; + NWilson::TSpan Span; + + TRequestResponse() = default; + TRequestResponse(NWilson::TSpan&& span) + : Span(std::move(span)) + {} + + TRequestResponse(const TRequestResponse&) = delete; + TRequestResponse(TRequestResponse&&) = default; + TRequestResponse& operator =(const TRequestResponse&) = delete; + TRequestResponse& operator =(TRequestResponse&&) = default; + + void Set(std::unique_ptr&& response) { + constexpr bool hasErrorCheck = requires(const std::unique_ptr& r) {TSelfCheckRequest::IsSuccess(r);}; + if constexpr (hasErrorCheck) { + if (!TSelfCheckRequest::IsSuccess(response)) { + Error(TSelfCheckRequest::GetError(response)); + return; + } + } + if (!IsDone()) { + Span.EndOk(); + } + Response = std::move(response); + } + + void Set(TAutoPtr>&& response) { + Set(std::unique_ptr(response->Release().Release())); + } + + bool Error(const TString& error) { + if (!IsDone()) { + Span.EndError(error); + Response = error; + return true; + } + return false; + } + + bool IsOk() const { + return std::holds_alternative>(Response); + } + + bool IsError() const { + return std::holds_alternative(Response); + } + + bool IsDone() const { + return Response.index() != 0; + } + + explicit operator bool() const { + return IsOk(); + } + + T* Get() { + return std::get>(Response).get(); + } + + const T* Get() const { + return std::get>(Response).get(); + } + + T& GetRef() { + return *Get(); + } + + const T& GetRef() const { + return *Get(); + } + + T* operator ->() { + return Get(); + } + + const T* operator ->() const { + return Get(); + } + + T& operator *() { + return GetRef(); + } + + const T& operator *() const { + return GetRef(); + } + + TString GetError() const { + return std::get(Response); + } + + void Event(const TString& name) { + if (Span) { + Span.Event(name); + } + } + }; + + static bool IsSuccess(const std::unique_ptr& ev) { + return (ev->Request->ResultSet.size() > 0) && (std::find_if(ev->Request->ResultSet.begin(), ev->Request->ResultSet.end(), + [](const auto& entry) { + return entry.Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok; + }) != ev->Request->ResultSet.end()); + } + + static TString GetError(const std::unique_ptr& ev) { + if (ev->Request->ResultSet.size() == 0) { + return "empty response"; + } + for (const auto& entry : ev->Request->ResultSet) { + if (entry.Status != NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { + switch (entry.Status) { + case NSchemeCache::TSchemeCacheNavigate::EStatus::Ok: + return "Ok"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::Unknown: + return "Unknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::RootUnknown: + return "RootUnknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathErrorUnknown: + return "PathErrorUnknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotTable: + return "PathNotTable"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotPath: + return "PathNotPath"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::TableCreationNotComplete: + return "TableCreationNotComplete"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::LookupError: + return "LookupError"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::RedirectLookupError: + return "RedirectLookupError"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::AccessDenied: + return "AccessDenied"; + default: + return ::ToString(static_cast(entry.Status)); + } + } + } + return "no error"; + } + + static bool IsSuccess(const std::unique_ptr& ev) { + return ev->GetRecord().status() == NKikimrScheme::StatusSuccess; + } + + static TString GetError(const std::unique_ptr& ev) { + return NKikimrScheme::EStatus_Name(ev->GetRecord().status()); + } + + static bool IsSuccess(const std::unique_ptr& ev) { + const auto& operation(ev->Record.GetResponse().operation()); + return operation.ready() && operation.status() == Ydb::StatusIds::SUCCESS; + } + + static TString GetError(const std::unique_ptr& ev) { + const auto& operation(ev->Record.GetResponse().operation()); + return Ydb::StatusIds_StatusCode_Name(operation.status()); + } + TString FilterDatabase; THashMap FilterDomainKey; TVector PipeClients; @@ -449,21 +619,23 @@ class TSelfCheckRequest : public TActorBootstrapped { TTabletId RootSchemeShardId; TTabletId RootHiveId; THashMap TenantByPath; - THashMap> DescribeByPath; + THashMap> DescribeByPath; THashMap> PathsByPoolName; + THashMap> TenantStatusByPath; THashMap DatabaseStatusByPath; THashMap> TenantStateByPath; - THashMap> NavigateResult; - THashMap> HiveDomainStats; - THashMap> HiveNodeStats; - THashMap> HiveInfo; - THolder NodesInfo; + THashMap NavigateResult; + THashMap> HiveDomainStats; + THashMap> HiveNodeStats; + THashMap> HiveInfo; + std::optional> ListTenants; + std::optional> NodesInfo; THashMap MergedNodeInfo; - std::optional StoragePools; - std::optional Groups; - std::optional VSlots; - std::optional PDisks; - bool RequestedStorageConfig = false; + std::optional> StoragePools; + std::optional> Groups; + std::optional> VSlots; + std::optional> PDisks; + std::optional> NodeWardenStorageConfig; THashSet UnknownStaticGroups; THashSet NodeIds; @@ -476,7 +648,7 @@ class TSelfCheckRequest : public TActorBootstrapped { THashMap DatabaseState; THashMap SharedDatabases; - THashMap> NodeSystemState; + THashMap> NodeSystemState; THashMap MergedNodeSystemState; std::unordered_map PDisksMap; @@ -493,15 +665,15 @@ class TSelfCheckRequest : public TActorBootstrapped { THashSet UnavailableStorageNodes; THashSet UnavailableComputeNodes; - THashMap> NodeVDiskState; + THashMap> NodeVDiskState; TList VDisksAppended; std::unordered_map MergedVDiskState; - THashMap> NodePDiskState; + THashMap> NodePDiskState; TList PDisksAppended; std::unordered_map MergedPDiskState; - THashMap> NodeBSGroupState; + THashMap> NodeBSGroupState; TList BSGroupAppended; std::unordered_map MergedBSGroupState; @@ -602,9 +774,9 @@ class TSelfCheckRequest : public TActorBootstrapped { TTenantInfo& tenant = TenantByPath[DomainPath]; tenant.Name = DomainPath; RequestSchemeCacheNavigate(DomainPath); - RequestListTenants(); + ListTenants = RequestListTenants(); } else if (FilterDatabase != DomainPath) { - RequestTenantStatus(FilterDatabase); + TenantStatusByPath[FilterDatabase] = RequestTenantStatus(FilterDatabase); } else { TTenantInfo& tenant = TenantByPath[DomainPath]; tenant.Name = DomainPath; @@ -616,14 +788,14 @@ class TSelfCheckRequest : public TActorBootstrapped { TabletRequests.TabletStates[RootHiveId].Database = DomainPath; TabletRequests.TabletStates[RootHiveId].Type = TTabletTypes::Hive; //RequestHiveDomainStats(RootHiveId); - RequestHiveNodeStats(RootHiveId); - RequestHiveInfo(RootHiveId); + HiveNodeStats[RootHiveId] = RequestHiveNodeStats(RootHiveId); + HiveInfo[RootHiveId] = RequestHiveInfo(RootHiveId); } if (RootSchemeShardId && !IsSpecificDatabaseFilter()) { TabletRequests.TabletStates[RootSchemeShardId].Database = DomainPath; TabletRequests.TabletStates[RootSchemeShardId].Type = TTabletTypes::SchemeShard; - RequestDescribe(RootSchemeShardId, DomainPath); + DescribeByPath[DomainPath] = RequestDescribe(RootSchemeShardId, DomainPath); } if (BsControllerId) { @@ -632,7 +804,9 @@ class TSelfCheckRequest : public TActorBootstrapped { RequestBsController(); } - Send(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); + + NodesInfo = TRequestResponse(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, "TEvInterconnect::TEvListNodes")); + Send(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes(), 0/*flags*/, 0/*cookie*/, Span.GetTraceId()); ++Requests; Become(&TThis::StateWait); @@ -641,7 +815,7 @@ class TSelfCheckRequest : public TActorBootstrapped { } bool HaveAllBSControllerInfo() { - return StoragePools && Groups && VSlots && PDisks; + return StoragePools && StoragePools->IsOk() && Groups && Groups->IsOk() && VSlots && VSlots->IsOk() && PDisks && PDisks->IsOk(); } bool NeedWhiteboardInfoForGroup(TGroupId groupId) { @@ -649,7 +823,8 @@ class TSelfCheckRequest : public TActorBootstrapped { } void Handle(TEvNodeWardenStorageConfig::TPtr ev) { - if (const NKikimrBlobStorage::TStorageConfig& config = *ev->Get()->Config; config.HasBlobStorageConfig()) { + NodeWardenStorageConfig->Set(std::move(ev)); + if (const NKikimrBlobStorage::TStorageConfig& config = *NodeWardenStorageConfig->Get()->Config; config.HasBlobStorageConfig()) { if (const auto& bsConfig = config.GetBlobStorageConfig(); bsConfig.HasServiceSet()) { const auto& staticConfig = bsConfig.GetServiceSet(); for (const NKikimrBlobStorage::TNodeWardenServiceSet_TPDisk& pDisk : staticConfig.pdisks()) { @@ -740,10 +915,11 @@ class TSelfCheckRequest : public TActorBootstrapped { } } - void RequestTabletPipe(TTabletId tabletId, - const TString& key, - IEventBase* payload, - std::optional requestId = std::nullopt) { + template + [[nodiscard]] TRequestResponse RequestTabletPipe(TTabletId tabletId, + IEventBase* payload, + std::optional requestId = std::nullopt) { + TString key = TypeName(*payload); ui64 cookie; if (requestId) { cookie = *requestId; @@ -751,6 +927,10 @@ class TSelfCheckRequest : public TActorBootstrapped { } else { cookie = TabletRequests.MakeRequest(tabletId, key); } + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, key)); + if (Span) { + response.Span.Attribute("tablet_id", ::ToString(tabletId)); + } TTabletRequestsState::TTabletState& requestState(TabletRequests.TabletStates[tabletId]); if (!requestState.TabletPipe) { requestState.TabletPipe = RegisterWithSameMailbox(NTabletPipe::CreateClient( @@ -759,93 +939,123 @@ class TSelfCheckRequest : public TActorBootstrapped { NTabletPipe::TClientRetryPolicy::WithRetries())); PipeClients.emplace_back(requestState.TabletPipe); } - NTabletPipe::SendData(SelfId(), requestState.TabletPipe, payload, cookie); + NTabletPipe::SendData(SelfId(), requestState.TabletPipe, payload, cookie, response.Span.GetTraceId()); ++Requests; + return response; } - void RequestDescribe(TTabletId schemeShardId, const TString& path) { + [[nodiscard]] TRequestResponse RequestDescribe(TTabletId schemeShardId, const TString& path) { THolder request = MakeHolder(); NKikimrSchemeOp::TDescribePath& record = request->Record; record.SetPath(path); record.MutableOptions()->SetReturnPartitioningInfo(false); record.MutableOptions()->SetReturnPartitionConfig(false); record.MutableOptions()->SetReturnChildren(false); - RequestTabletPipe(schemeShardId, "TEvDescribeScheme:" + path, request.Release()); + auto response = RequestTabletPipe(schemeShardId, request.Release()); + if (response.Span) { + response.Span.Attribute("path", path); + } + return response; } - void RequestHiveInfo(TTabletId hiveId) { + [[nodiscard]] TRequestResponse RequestHiveInfo(TTabletId hiveId) { THolder request = MakeHolder(); request->Record.SetReturnFollowers(true); - RequestTabletPipe(hiveId, "TEvRequestHiveInfo", request.Release()); + return RequestTabletPipe(hiveId, request.Release()); } - void RequestHiveDomainStats(TTabletId hiveId) { + [[nodiscard]] TRequestResponse RequestHiveDomainStats(TTabletId hiveId) { THolder request = MakeHolder(); request->Record.SetReturnFollowers(true); request->Record.SetReturnMetrics(true); - RequestTabletPipe(hiveId, "TEvRequestHiveDomainStats", request.Release()); + return RequestTabletPipe(hiveId, request.Release()); } - void RequestHiveNodeStats(TTabletId hiveId) { + [[nodiscard]] TRequestResponse RequestHiveNodeStats(TTabletId hiveId) { THolder request = MakeHolder(); - RequestTabletPipe(hiveId, "TEvRequestHiveNodeStats", request.Release()); + return RequestTabletPipe(hiveId, request.Release()); } - void RequestTenantStatus(const TString& path) { + [[nodiscard]] TRequestResponse RequestTenantStatus(const TString& path) { THolder request = MakeHolder(); request->Record.MutableRequest()->set_path(path); - RequestTabletPipe(ConsoleId, "TEvGetTenantStatusRequest:" + path, request.Release()); + auto response = RequestTabletPipe(ConsoleId, request.Release()); + if (response.Span) { + response.Span.Attribute("path", path); + } + return response; } - void RequestListTenants() { + [[nodiscard]] TRequestResponse RequestListTenants() { THolder request = MakeHolder(); - RequestTabletPipe(ConsoleId, "TEvListTenantsRequest", request.Release()); + return RequestTabletPipe(ConsoleId, request.Release()); } void RequestBsController() { THolder requestPools = MakeHolder(); - RequestTabletPipe(BsControllerId, "TEvGetStoragePoolsRequest", requestPools.Release(), TTabletRequestsState::RequestStoragePools); + StoragePools = RequestTabletPipe(BsControllerId, requestPools.Release(), TTabletRequestsState::RequestStoragePools); THolder requestGroups = MakeHolder(); - RequestTabletPipe(BsControllerId, "TEvGetGroupsRequest", requestGroups.Release(), TTabletRequestsState::RequestGroups); + Groups = RequestTabletPipe(BsControllerId, requestGroups.Release(), TTabletRequestsState::RequestGroups); THolder requestVSlots = MakeHolder(); - RequestTabletPipe(BsControllerId, "TEvGetVSlotsRequest", requestVSlots.Release(), TTabletRequestsState::RequestVSlots); + VSlots = RequestTabletPipe(BsControllerId, requestVSlots.Release(), TTabletRequestsState::RequestVSlots); THolder requestPDisks = MakeHolder(); - RequestTabletPipe(BsControllerId, "TEvGetPDisksRequest", requestPDisks.Release(), TTabletRequestsState::RequestPDisks); + PDisks = RequestTabletPipe(BsControllerId, requestPDisks.Release(), TTabletRequestsState::RequestPDisks); } + THashMap> NavigateKeySet; + void RequestSchemeCacheNavigate(const TString& path) { - THolder request = MakeHolder(); - NSchemeCache::TSchemeCacheNavigate::TEntry entry; + ui64 cookie = NavigateKeySet.size(); + THolder request = MakeHolder(); + request->Cookie = cookie; + TSchemeCacheNavigate::TEntry& entry = request->ResultSet.emplace_back(); entry.Path = NKikimr::SplitPath(path); - entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; - request->ResultSet.emplace_back(entry); - Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); + entry.Operation = TSchemeCacheNavigate::EOp::OpPath; + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, TypeName(*request.Get()))); + if (Span) { + response.Span.Attribute("path", path); + } + NavigateKeySet.emplace(cookie, std::move(response)); + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release()), 0/*flags*/, 0/*cookie*/, response.Span.GetTraceId()); ++Requests; } void RequestSchemeCacheNavigate(const TPathId& pathId) { + ui64 cookie = NavigateKeySet.size(); THolder request = MakeHolder(); - NSchemeCache::TSchemeCacheNavigate::TEntry entry; + request->Cookie = cookie; + NSchemeCache::TSchemeCacheNavigate::TEntry& entry = request->ResultSet.emplace_back(); entry.TableId.PathId = pathId; entry.RequestType = NSchemeCache::TSchemeCacheNavigate::TEntry::ERequestType::ByTableId; entry.RedirectRequired = false; entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; - request->ResultSet.emplace_back(entry); - Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, TypeName(*request.Get()))); + if (Span) { + response.Span.Attribute("path_id", pathId.ToString()); + } + NavigateKeySet.emplace(cookie, std::move(response)); + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release()), 0/*flags*/, 0/*cookie*/, response.Span.GetTraceId()); ++Requests; } template - void RequestNodeWhiteboard(TNodeId nodeId) { + [[nodiscard]] TRequestResponse::Type> RequestNodeWhiteboard(TNodeId nodeId) { TActorId whiteboardServiceId = NNodeWhiteboard::MakeNodeWhiteboardServiceId(nodeId); auto request = MakeHolder(); - Send(whiteboardServiceId, request.Release(), IEventHandle::FlagTrackDelivery, nodeId); + TRequestResponse::Type> response(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, TypeName(*request.Get()))); + if (response.Span) { + response.Span.Attribute("target_node_id", nodeId); + } + Send(whiteboardServiceId, request.Release(), IEventHandle::FlagTrackDelivery, nodeId, response.Span.GetTraceId()); + return response; } void RequestGenericNode(TNodeId nodeId) { - if (NodeIds.emplace(nodeId).second) { - Send(TlsActivationContext->ActorSystem()->InterconnectProxy(nodeId), new TEvents::TEvSubscribe()); - RequestNodeWhiteboard(nodeId); + if (NodeSystemState.count(nodeId) == 0) { + if (NodeIds.insert(nodeId).second) { + Send(TlsActivationContext->ActorSystem()->InterconnectProxy(nodeId), new TEvents::TEvSubscribe()); + } + NodeSystemState.emplace(nodeId, RequestNodeWhiteboard(nodeId)); ++Requests; } } @@ -859,36 +1069,47 @@ class TSelfCheckRequest : public TActorBootstrapped { void RequestStorageNode(TNodeId nodeId) { if (StorageNodeIds.emplace(nodeId).second) { RequestGenericNode(nodeId); - RequestNodeWhiteboard(nodeId); - ++Requests; - RequestNodeWhiteboard(nodeId); - ++Requests; - RequestNodeWhiteboard(nodeId); - ++Requests; + if (NodeVDiskState.count(nodeId) == 0) { + NodeVDiskState.emplace(nodeId, RequestNodeWhiteboard(nodeId)); + ++Requests; + } + if (NodePDiskState.count(nodeId) == 0) { + NodePDiskState.emplace(nodeId, RequestNodeWhiteboard(nodeId)); + ++Requests; + } + if (NodeBSGroupState.count(nodeId) == 0) { + NodeBSGroupState.emplace(nodeId, RequestNodeWhiteboard(nodeId)); + ++Requests; + } } } - void RequestStorageConfig() { - if (!RequestedStorageConfig) { - Send(MakeBlobStorageNodeWardenID(SelfId().NodeId()), new TEvNodeWardenQueryStorageConfig(false)); - RequestedStorageConfig = true; - ++Requests; - } + [[nodiscard]] TRequestResponse RequestStorageConfig() { + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::TTablet::Detailed, TypeName())); + Send(MakeBlobStorageNodeWardenID(SelfId().NodeId()), new TEvNodeWardenQueryStorageConfig(false), 0/*flags*/, 0/*cookie*/, response.Span.GetTraceId()); + ++Requests; + return response; } void Handle(TEvPrivate::TEvRetryNodeWhiteboard::TPtr& ev) { - switch (ev->Get()->EventId) { + auto eventId = ev->Get()->EventId; + auto nodeId = ev->Get()->NodeId; + switch (eventId) { case NNodeWhiteboard::TEvWhiteboard::EvSystemStateRequest: - RequestNodeWhiteboard(ev->Get()->NodeId); + NodeSystemState.erase(nodeId); + NodeSystemState[nodeId] = RequestNodeWhiteboard(nodeId); break; case NNodeWhiteboard::TEvWhiteboard::EvVDiskStateRequest: - RequestNodeWhiteboard(ev->Get()->NodeId); + NodeVDiskState.erase(nodeId); + NodeVDiskState[nodeId] = RequestNodeWhiteboard(nodeId); break; case NNodeWhiteboard::TEvWhiteboard::EvPDiskStateRequest: - RequestNodeWhiteboard(ev->Get()->NodeId); + NodePDiskState.erase(nodeId); + NodePDiskState[nodeId] = RequestNodeWhiteboard(nodeId); break; case NNodeWhiteboard::TEvWhiteboard::EvBSGroupStateRequest: - RequestNodeWhiteboard(ev->Get()->NodeId); + NodeBSGroupState.erase(nodeId); + NodeBSGroupState[nodeId] = RequestNodeWhiteboard(nodeId); break; default: RequestDone("unsupported event scheduled"); @@ -907,37 +1128,34 @@ class TSelfCheckRequest : public TActorBootstrapped { void Handle(TEvents::TEvUndelivered::TPtr& ev) { ui32 nodeId = ev.Get()->Cookie; + TString error = "Undelivered"; if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvSystemStateRequest) { - if (NodeIds.count(nodeId) != 0 && NodeSystemState.count(nodeId) == 0) { + if (NodeSystemState.count(nodeId) && NodeSystemState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeSystemState.emplace(nodeId, nullptr); RequestDone("undelivered of TEvSystemStateRequest"); UnavailableComputeNodes.insert(nodeId); } } } if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvVDiskStateRequest) { - if (StorageNodeIds.count(nodeId) != 0 && NodeVDiskState.count(nodeId) == 0) { + if (NodeVDiskState.count(nodeId) && NodeVDiskState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeVDiskState.emplace(nodeId, nullptr); RequestDone("undelivered of TEvVDiskStateRequest"); UnavailableStorageNodes.insert(nodeId); } } } if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvPDiskStateRequest) { - if (StorageNodeIds.count(nodeId) != 0 && NodePDiskState.count(nodeId) == 0) { + if (NodePDiskState.count(nodeId) && NodePDiskState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodePDiskState.emplace(nodeId, nullptr); RequestDone("undelivered of TEvPDiskStateRequest"); UnavailableStorageNodes.insert(nodeId); } } } if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvBSGroupStateRequest) { - if (StorageNodeIds.count(nodeId) != 0 && NodeBSGroupState.count(nodeId) == 0) { + if (NodeBSGroupState.count(nodeId) && NodeBSGroupState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeBSGroupState.emplace(nodeId, nullptr); RequestDone("undelivered of TEvBSGroupStateRequest"); } } @@ -946,30 +1164,27 @@ class TSelfCheckRequest : public TActorBootstrapped { void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { ui32 nodeId = ev->Get()->NodeId; - if (NodeIds.count(nodeId) != 0 && NodeSystemState.count(nodeId) == 0) { + TString error = "NodeDisconnected"; + if (NodeSystemState.count(nodeId) && NodeSystemState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeSystemState.emplace(nodeId, nullptr); RequestDone("node disconnected with TEvSystemStateRequest"); UnavailableComputeNodes.insert(nodeId); } } - if (StorageNodeIds.count(nodeId) != 0 && NodeVDiskState.count(nodeId) == 0) { + if (NodeVDiskState.count(nodeId) && NodeVDiskState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeVDiskState.emplace(nodeId, nullptr); RequestDone("node disconnected with TEvVDiskStateRequest"); UnavailableStorageNodes.insert(nodeId); } } - if (StorageNodeIds.count(nodeId) != 0 && NodePDiskState.count(nodeId) == 0) { + if (NodePDiskState.count(nodeId) && NodePDiskState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodePDiskState.emplace(nodeId, nullptr); RequestDone("node disconnected with TEvPDiskStateRequest"); UnavailableStorageNodes.insert(nodeId); } } - if (StorageNodeIds.count(nodeId) != 0 && NodeBSGroupState.count(nodeId) == 0) { + if (NodeBSGroupState.count(nodeId) && NodeBSGroupState[nodeId].Error(error)) { if (!RetryRequestNodeWhiteboard(nodeId)) { - NodeBSGroupState.emplace(nodeId, nullptr); RequestDone("node disconnected with TEvBSGroupStateRequest"); } } @@ -992,11 +1207,15 @@ class TSelfCheckRequest : public TActorBootstrapped { void HandleTimeout(TEvents::TEvWakeup::TPtr& ev) { switch (ev->Get()->Tag) { case TimeoutBSC: + Span.Event("TimeoutBSC"); if (!HaveAllBSControllerInfo()) { - RequestStorageConfig(); + if (!NodeWardenStorageConfig) { + NodeWardenStorageConfig = RequestStorageConfig(); + } } break; case TimeoutFinal: + Span.Event("TimeoutFinal"); ReplyAndPassAway(); break; } @@ -1013,8 +1232,8 @@ class TSelfCheckRequest : public TActorBootstrapped { void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { bool needComputeFromStaticNodes = !IsSpecificDatabaseFilter(); - NodesInfo = ev->Release(); - for (const auto& ni : NodesInfo->Nodes) { + NodesInfo->Set(std::move(ev)); + for (const auto& ni : NodesInfo->Get()->Nodes) { MergedNodeInfo[ni.NodeId] = ∋ if (IsStaticNode(ni.NodeId) && needComputeFromStaticNodes) { DatabaseState[DomainPath].ComputeNodeIds.push_back(ni.NodeId); @@ -1029,44 +1248,46 @@ class TSelfCheckRequest : public TActorBootstrapped { } bool NeedWhiteboardForStaticGroupsWithUnknownStatus() { - return RequestedStorageConfig && !IsSpecificDatabaseFilter(); + return NodeWardenStorageConfig && !IsSpecificDatabaseFilter(); } void Handle(NSysView::TEvSysView::TEvGetStoragePoolsResponse::TPtr& ev) { TabletRequests.CompleteRequest(TTabletRequestsState::RequestStoragePools); - StoragePools = std::move(ev->Get()->Record); + StoragePools->Set(std::move(ev)); AggregateBSControllerState(); RequestDone("TEvGetStoragePoolsRequest"); } void Handle(NSysView::TEvSysView::TEvGetGroupsResponse::TPtr& ev) { TabletRequests.CompleteRequest(TTabletRequestsState::RequestGroups); - Groups = std::move(ev->Get()->Record); + Groups->Set(std::move(ev)); AggregateBSControllerState(); RequestDone("TEvGetGroupsRequest"); } void Handle(NSysView::TEvSysView::TEvGetVSlotsResponse::TPtr& ev) { TabletRequests.CompleteRequest(TTabletRequestsState::RequestVSlots); - VSlots = std::move(ev->Get()->Record); + VSlots->Set(std::move(ev)); AggregateBSControllerState(); RequestDone("TEvGetVSlotsRequest"); } void Handle(NSysView::TEvSysView::TEvGetPDisksResponse::TPtr& ev) { TabletRequests.CompleteRequest(TTabletRequestsState::RequestPDisks); - PDisks = std::move(ev->Get()->Record); + PDisks->Set(std::move(ev)); AggregateBSControllerState(); RequestDone("TEvGetPDisksRequest"); } void Handle(NSchemeShard::TEvSchemeShard::TEvDescribeSchemeResult::TPtr& ev) { TabletRequests.CompleteRequest(ev->Cookie); - if (ev->Get()->GetRecord().status() == NKikimrScheme::StatusSuccess) { - TString path = ev->Get()->GetRecord().path(); + TString path = ev->Get()->GetRecord().path(); + auto& response = DescribeByPath[path]; + response.Set(std::move(ev)); + if (response.IsOk()) { TDatabaseState& state(DatabaseState[path]); state.Path = path; - for (const auto& storagePool : ev->Get()->GetRecord().pathdescription().domaindescription().storagepools()) { + for (const auto& storagePool : response.Get()->GetRecord().pathdescription().domaindescription().storagepools()) { TString storagePoolName = storagePool.name(); state.StoragePoolNames.emplace(storagePoolName); PathsByPoolName[storagePoolName].emplace(path); // no poolId in TEvDescribeSchemeResult, so it's neccesary to keep poolNames instead @@ -1076,18 +1297,19 @@ class TSelfCheckRequest : public TActorBootstrapped { state.StoragePools.emplace(0); // static group has poolId = 0 StoragePoolState[0].Name = STATIC_STORAGE_POOL_NAME; } - state.StorageUsage = ev->Get()->GetRecord().pathdescription().domaindescription().diskspaceusage().tables().totalsize(); - state.StorageQuota = ev->Get()->GetRecord().pathdescription().domaindescription().databasequotas().data_size_hard_quota(); - - DescribeByPath[path] = ev->Release(); + state.StorageUsage = response.Get()->GetRecord().pathdescription().domaindescription().diskspaceusage().tables().totalsize(); + state.StorageQuota = response.Get()->GetRecord().pathdescription().domaindescription().databasequotas().data_size_hard_quota(); } RequestDone("TEvDescribeSchemeResult"); } void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { - if (ev->Get()->Request->ResultSet.size() == 1 && ev->Get()->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { - auto domainInfo = ev->Get()->Request->ResultSet.begin()->DomainInfo; - TString path = CanonizePath(ev->Get()->Request->ResultSet.begin()->Path); + TRequestResponse& response = NavigateKeySet[ev->Get()->Request->Cookie]; + response.Set(std::move(ev)); + if (response.IsOk()) { + auto domainInfo = response.Get()->Request->ResultSet.begin()->DomainInfo; + TString path = CanonizePath(response.Get()->Request->ResultSet.begin()->Path); + NavigateResult[path] = response.Get()->Request->Cookie; if (domainInfo->IsServerless()) { if (NeedHealthCheckForServerless(domainInfo)) { if (SharedDatabases.emplace(domainInfo->ResourcesDomainKey, path).second) { @@ -1108,11 +1330,14 @@ class TSelfCheckRequest : public TActorBootstrapped { TabletRequests.TabletStates[hiveId].Database = path; TabletRequests.TabletStates[hiveId].Type = TTabletTypes::Hive; //RequestHiveDomainStats(hiveId); - RequestHiveNodeStats(hiveId); - RequestHiveInfo(hiveId); + if (HiveNodeStats.count(hiveId) == 0) { + HiveNodeStats[hiveId] = RequestHiveNodeStats(hiveId); + } + if (HiveInfo.count(hiveId) == 0) { + HiveInfo[hiveId] = RequestHiveInfo(hiveId); + } } FilterDomainKey[TSubDomainKey(domainInfo->DomainKey.OwnerId, domainInfo->DomainKey.LocalPathId)] = path; - NavigateResult[path] = std::move(ev->Get()->Request); TTabletId schemeShardId = domainInfo->Params.GetSchemeShard(); if (!schemeShardId) { schemeShardId = RootSchemeShardId; @@ -1120,7 +1345,9 @@ class TSelfCheckRequest : public TActorBootstrapped { TabletRequests.TabletStates[schemeShardId].Database = path; TabletRequests.TabletStates[schemeShardId].Type = TTabletTypes::SchemeShard; } - RequestDescribe(schemeShardId, path); + if (DescribeByPath.count(path) == 0) { + DescribeByPath[path] = RequestDescribe(schemeShardId, path); + } } RequestDone("TEvNavigateKeySetResult"); } @@ -1132,53 +1359,62 @@ class TSelfCheckRequest : public TActorBootstrapped { void Handle(TEvHive::TEvResponseHiveDomainStats::TPtr& ev) { TTabletId hiveId = TabletRequests.CompleteRequest(ev->Cookie); - for (const NKikimrHive::THiveDomainStats& hiveStat : ev->Get()->Record.GetDomainStats()) { + auto& response = HiveDomainStats[hiveId]; + response.Set(std::move(ev)); + for (const NKikimrHive::THiveDomainStats& hiveStat : response.Get()->Record.GetDomainStats()) { for (TNodeId nodeId : hiveStat.GetNodeIds()) { RequestComputeNode(nodeId); } } - HiveDomainStats[hiveId] = std::move(ev->Release()); RequestDone("TEvResponseHiveDomainStats"); } void Handle(TEvHive::TEvResponseHiveNodeStats::TPtr& ev) { TTabletId hiveId = TabletRequests.CompleteRequest(ev->Cookie); + auto& response = HiveNodeStats[hiveId]; + response.Set(std::move(ev)); TInstant aliveBarrier = TInstant::Now() - TDuration::Minutes(5); - for (const NKikimrHive::THiveNodeStats& hiveStat : ev->Get()->Record.GetNodeStats()) { + for (const NKikimrHive::THiveNodeStats& hiveStat : response.Get()->Record.GetNodeStats()) { if (!hiveStat.HasLastAliveTimestamp() || TInstant::MilliSeconds(hiveStat.GetLastAliveTimestamp()) > aliveBarrier) { RequestComputeNode(hiveStat.GetNodeId()); } } - HiveNodeStats[hiveId] = std::move(ev->Release()); RequestDone("TEvResponseHiveNodeStats"); } void Handle(TEvHive::TEvResponseHiveInfo::TPtr& ev) { TTabletId hiveId = TabletRequests.CompleteRequest(ev->Cookie); - HiveInfo[hiveId] = std::move(ev->Release()); + HiveInfo[hiveId].Set(std::move(ev)); RequestDone("TEvResponseHiveInfo"); } void Handle(NConsole::TEvConsole::TEvGetTenantStatusResponse::TPtr& ev) { TabletRequests.CompleteRequest(ev->Cookie); auto& operation(ev->Get()->Record.GetResponse().operation()); - if (operation.ready() && operation.status() == Ydb::StatusIds::SUCCESS) { + if (operation.ready()) { Ydb::Cms::GetDatabaseStatusResult getTenantStatusResult; operation.result().UnpackTo(&getTenantStatusResult); TString path = getTenantStatusResult.path(); - DatabaseStatusByPath[path] = std::move(getTenantStatusResult); - DatabaseState[path]; - RequestSchemeCacheNavigate(path); + auto& response = TenantStatusByPath[path]; + response.Set(std::move(ev)); + if (response.IsOk()) { + Ydb::Cms::GetDatabaseStatusResult getTenantStatusResult; + operation.result().UnpackTo(&getTenantStatusResult); + DatabaseStatusByPath[path] = getTenantStatusResult; + DatabaseState[path]; + RequestSchemeCacheNavigate(path); + } } RequestDone("TEvGetTenantStatusResponse"); } void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { TabletRequests.CompleteRequest(ev->Cookie); + ListTenants->Set(std::move(ev)); Ydb::Cms::ListDatabasesResult listTenantsResult; - ev->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); + ListTenants->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); for (const TString& path : listTenantsResult.paths()) { - RequestTenantStatus(path); + TenantStatusByPath[path] = RequestTenantStatus(path); DatabaseState[path]; } RequestDone("TEvListTenantsResponse"); @@ -1186,20 +1422,18 @@ class TSelfCheckRequest : public TActorBootstrapped { void Handle(NNodeWhiteboard::TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { TNodeId nodeId = ev.Get()->Cookie; - if (NodeSystemState.count(nodeId) == 0) { - auto& nodeSystemState(NodeSystemState[nodeId]); - nodeSystemState = ev->Release(); - for (NKikimrWhiteboard::TSystemStateInfo& state : *nodeSystemState->Record.MutableSystemStateInfo()) { - state.set_nodeid(nodeId); - MergedNodeSystemState[nodeId] = &state; - } - RequestDone("TEvSystemStateResponse"); + auto& nodeSystemState(NodeSystemState[nodeId]); + nodeSystemState.Set(std::move(ev)); + for (NKikimrWhiteboard::TSystemStateInfo& state : *nodeSystemState->Record.MutableSystemStateInfo()) { + state.set_nodeid(nodeId); + MergedNodeSystemState[nodeId] = &state; } + RequestDone("TEvSystemStateResponse"); } static const int HIVE_SYNCHRONIZATION_PERIOD_MS = 10000; - bool IsHiveSynchronizationPeriod(NKikimrHive::TEvResponseHiveInfo& hiveInfo) { + bool IsHiveSynchronizationPeriod(const NKikimrHive::TEvResponseHiveInfo& hiveInfo) { return hiveInfo.GetResponseTimestamp() < hiveInfo.GetStartTimeTimestamp() + HIVE_SYNCHRONIZATION_PERIOD_MS; } @@ -1207,7 +1441,7 @@ class TSelfCheckRequest : public TActorBootstrapped { TNodeTabletState::TTabletStateSettings settings; settings.AliveBarrier = TInstant::Now() - TDuration::Minutes(5); for (const auto& [hiveId, hiveResponse] : HiveInfo) { - if (hiveResponse) { + if (hiveResponse.IsOk()) { settings.IsHiveSynchronizationPeriod = IsHiveSynchronizationPeriod(hiveResponse->Record); for (const NKikimrHive::TTabletInfo& hiveTablet : hiveResponse->Record.GetTablets()) { TSubDomainKey tenantId = TSubDomainKey(hiveTablet.GetObjectDomain()); @@ -1286,7 +1520,7 @@ class TSelfCheckRequest : public TActorBootstrapped { if (!HaveAllBSControllerInfo()) { return; } - for (const auto& group : Groups->GetEntries()) { + for (const auto& group : Groups->Get()->Record.GetEntries()) { auto groupId = group.GetKey().GetGroupId(); auto poolId = group.GetInfo().GetStoragePoolId(); auto& groupState = GroupState[groupId]; @@ -1294,19 +1528,19 @@ class TSelfCheckRequest : public TActorBootstrapped { groupState.Generation = group.GetInfo().GetGeneration(); StoragePoolState[poolId].Groups.emplace(groupId); } - for (const auto& vSlot : VSlots->GetEntries()) { + for (const auto& vSlot : VSlots->Get()->Record.GetEntries()) { auto vSlotId = GetVSlotId(vSlot.GetKey()); auto groupStateIt = GroupState.find(vSlot.GetInfo().GetGroupId()); if (groupStateIt != GroupState.end() && vSlot.GetInfo().GetGroupGeneration() == groupStateIt->second.Generation) { groupStateIt->second.VSlots.push_back(&vSlot); } } - for (const auto& pool : StoragePools->GetEntries()) { // there is no specific pool for static group here + for (const auto& pool : StoragePools->Get()->Record.GetEntries()) { // there is no specific pool for static group here ui64 poolId = pool.GetKey().GetStoragePoolId(); TString storagePoolName = pool.GetInfo().GetName(); StoragePoolState[poolId].Name = storagePoolName; } - for (const auto& pDisk : PDisks->GetEntries()) { + for (const auto& pDisk : PDisks->Get()->Record.GetEntries()) { auto pDiskId = GetPDiskId(pDisk.GetKey()); PDisksMap.emplace(pDiskId, &pDisk); } @@ -1319,7 +1553,9 @@ class TSelfCheckRequest : public TActorBootstrapped { BLOG_D("Static group status is " << staticGroupStatus.overall()); if (staticGroupStatus.overall() != Ydb::Monitoring::StatusFlag::GREEN) { UnknownStaticGroups.emplace(0); - RequestStorageConfig(); + if (!NodeWardenStorageConfig) { + NodeWardenStorageConfig = RequestStorageConfig(); + } } } @@ -1848,53 +2084,47 @@ class TSelfCheckRequest : public TActorBootstrapped { void Handle(NNodeWhiteboard::TEvWhiteboard::TEvVDiskStateResponse::TPtr& ev) { TNodeId nodeId = ev.Get()->Cookie; - if (NodeVDiskState.count(nodeId) == 0) { - auto& nodeVDiskState(NodeVDiskState[nodeId]); - nodeVDiskState = ev->Release(); - for (NKikimrWhiteboard::TVDiskStateInfo& state : *nodeVDiskState->Record.MutableVDiskStateInfo()) { - state.set_nodeid(nodeId); - auto id = GetVDiskId(state.vdiskid()); - MergedVDiskState[id] = &state; - } - RequestDone("TEvVDiskStateResponse"); + auto& nodeVDiskState(NodeVDiskState[nodeId]); + nodeVDiskState.Set(std::move(ev)); + for (NKikimrWhiteboard::TVDiskStateInfo& state : *nodeVDiskState->Record.MutableVDiskStateInfo()) { + state.set_nodeid(nodeId); + auto id = GetVDiskId(state.vdiskid()); + MergedVDiskState[id] = &state; } + RequestDone("TEvVDiskStateResponse"); } void Handle(NNodeWhiteboard::TEvWhiteboard::TEvPDiskStateResponse::TPtr& ev) { TNodeId nodeId = ev.Get()->Cookie; - if (NodePDiskState.count(nodeId) == 0) { - auto& nodePDiskState(NodePDiskState[nodeId]); - nodePDiskState = ev->Release(); - for (NKikimrWhiteboard::TPDiskStateInfo& state : *nodePDiskState->Record.MutablePDiskStateInfo()) { - state.set_nodeid(nodeId); - auto id = GetPDiskId(state); - MergedPDiskState[id] = &state; - } - RequestDone("TEvPDiskStateResponse"); + auto& nodePDiskState(NodePDiskState[nodeId]); + nodePDiskState.Set(std::move(ev)); + for (NKikimrWhiteboard::TPDiskStateInfo& state : *nodePDiskState->Record.MutablePDiskStateInfo()) { + state.set_nodeid(nodeId); + auto id = GetPDiskId(state); + MergedPDiskState[id] = &state; } + RequestDone("TEvPDiskStateResponse"); } void Handle(NNodeWhiteboard::TEvWhiteboard::TEvBSGroupStateResponse::TPtr& ev) { ui64 nodeId = ev.Get()->Cookie; - if (NodeBSGroupState.count(nodeId) == 0) { - auto& nodeBSGroupState(NodeBSGroupState[nodeId]); - nodeBSGroupState = ev->Release(); - for (NKikimrWhiteboard::TBSGroupStateInfo& state : *nodeBSGroupState->Record.MutableBSGroupStateInfo()) { - state.set_nodeid(nodeId); - TString storagePoolName = state.storagepoolname(); - TGroupID groupId(state.groupid()); - const NKikimrWhiteboard::TBSGroupStateInfo*& current(MergedBSGroupState[state.groupid()]); - if (current == nullptr || current->GetGroupGeneration() < state.GetGroupGeneration()) { - current = &state; - } - if (storagePoolName.empty() && groupId.ConfigurationType() != EGroupConfigurationType::Static) { - continue; - } - StoragePoolStateByName[storagePoolName].Groups.emplace(state.groupid()); - StoragePoolStateByName[storagePoolName].Name = storagePoolName; + auto& nodeBSGroupState(NodeBSGroupState[nodeId]); + nodeBSGroupState.Set(std::move(ev)); + for (NKikimrWhiteboard::TBSGroupStateInfo& state : *nodeBSGroupState->Record.MutableBSGroupStateInfo()) { + state.set_nodeid(nodeId); + TString storagePoolName = state.storagepoolname(); + TGroupID groupId(state.groupid()); + const NKikimrWhiteboard::TBSGroupStateInfo*& current(MergedBSGroupState[state.groupid()]); + if (current == nullptr || current->GetGroupGeneration() < state.GetGroupGeneration()) { + current = &state; + } + if (storagePoolName.empty() && groupId.ConfigurationType() != EGroupConfigurationType::Static) { + continue; } - RequestDone("TEvBSGroupStateResponse"); + StoragePoolStateByName[storagePoolName].Groups.emplace(state.groupid()); + StoragePoolStateByName[storagePoolName].Name = storagePoolName; } + RequestDone("TEvBSGroupStateResponse"); } void FillPDiskStatusWithWhiteboard(const TString& pDiskId, const NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo, Ydb::Monitoring::StoragePDiskStatus& storagePDiskStatus, TSelfCheckContext context) { @@ -2736,6 +2966,7 @@ class TSelfCheckRequest : public TActorBootstrapped { } void ReplyAndPassAway() { + Span.Event("ReplyAndPassAway"); THolder response = MakeHolder(); Ydb::Monitoring::SelfCheckResult& result = response->Result; @@ -2996,7 +3227,7 @@ class THealthCheckService : public TActorBootstrapped { } void Handle(TEvSelfCheckRequest::TPtr& ev) { - Register(new TSelfCheckRequest(ev->Sender, ev.Get()->Release(), ev->Cookie)); + Register(new TSelfCheckRequest(ev->Sender, ev.Get()->Release(), ev->Cookie, std::move(ev->TraceId))); } std::shared_ptr GRpcClientLow; diff --git a/ydb/core/http_proxy/json_proto_conversion.h b/ydb/core/http_proxy/json_proto_conversion.h index 15a9142cc3ee..61e3ff14c20f 100644 --- a/ydb/core/http_proxy/json_proto_conversion.h +++ b/ydb/core/http_proxy/json_proto_conversion.h @@ -23,11 +23,9 @@ inline TString ProxyFieldNameConverter(const google::protobuf::FieldDescriptor& class TYdsProtoToJsonPrinter : public NProtobufJson::TProto2JsonPrinter { public: - TYdsProtoToJsonPrinter(const google::protobuf::Reflection* reflection, - const NProtobufJson::TProto2JsonConfig& config, + TYdsProtoToJsonPrinter(const NProtobufJson::TProto2JsonConfig& config, bool skipBase64Encode) : NProtobufJson::TProto2JsonPrinter(config) - , ProtoReflection(reflection) , SkipBase64Encode(skipBase64Encode) {} @@ -61,14 +59,15 @@ class TYdsProtoToJsonPrinter : public NProtobufJson::TProto2JsonPrinter { return Base64Encode(str); }; + auto* reflection = proto.GetReflection(); if (field.is_repeated()) { - for (int i = 0, endI = ProtoReflection->FieldSize(proto, &field); i < endI; ++i) { + for (int i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { PrintStringValue(field, TStringBuf(), - maybeBase64Encode(proto.GetReflection()->GetRepeatedString(proto, &field, i)), json); + maybeBase64Encode(reflection->GetRepeatedString(proto, &field, i)), json); } } else { PrintStringValue(field, key, - maybeBase64Encode(proto.GetReflection()->GetString(proto, &field)), json); + maybeBase64Encode(reflection->GetString(proto, &field)), json); } return; } @@ -82,13 +81,14 @@ class TYdsProtoToJsonPrinter : public NProtobufJson::TProto2JsonPrinter { key = MakeKey(field); } + auto* reflection = proto.GetReflection(); if (field.is_repeated()) { - for (int i = 0, endI = ProtoReflection->FieldSize(proto, &field); i < endI; ++i) { - double value = proto.GetReflection()->GetRepeatedInt64(proto, &field, i) / 1000.0; + for (int i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { + double value = reflection->GetRepeatedInt64(proto, &field, i) / 1000.0; PrintDoubleValue(TStringBuf(), value, json); } } else { - double value = proto.GetReflection()->GetInt64(proto, &field) / 1000.0; + double value = reflection->GetInt64(proto, &field) / 1000.0; PrintDoubleValue(key, value, json); } return; @@ -103,19 +103,20 @@ class TYdsProtoToJsonPrinter : public NProtobufJson::TProto2JsonPrinter { key = MakeKey(field); } + auto* reflection = proto.GetReflection(); if (field.is_repeated()) { - for (int i = 0, endI = ProtoReflection->FieldSize(proto, &field); i < endI; ++i) { - auto value = proto.GetReflection()->GetRepeatedString(proto, &field, i); + for (int i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { + auto value = reflection->GetRepeatedString(proto, &field, i); if (!value.empty()) { PrintStringValue(field, TStringBuf(), - proto.GetReflection()->GetRepeatedString(proto, &field, i), json); + reflection->GetRepeatedString(proto, &field, i), json); } } } else { - auto value = proto.GetReflection()->GetString(proto, &field); + auto value = reflection->GetString(proto, &field); if (!value.empty()) { PrintStringValue(field, key, - proto.GetReflection()->GetString(proto, &field), json); + reflection->GetString(proto, &field), json); } } return; @@ -126,7 +127,6 @@ class TYdsProtoToJsonPrinter : public NProtobufJson::TProto2JsonPrinter { } private: - const google::protobuf::Reflection* ProtoReflection = nullptr; bool SkipBase64Encode; }; @@ -137,7 +137,7 @@ inline void ProtoToJson(const NProtoBuf::Message& resp, NJson::TJsonValue& value .SetNameGenerator(ProxyFieldNameConverter) .SetMapAsObject(true) .SetEnumMode(NProtobufJson::TProto2JsonConfig::EnumName); - TYdsProtoToJsonPrinter printer(resp.GetReflection(), config, skipBase64Encode); + TYdsProtoToJsonPrinter printer(config, skipBase64Encode); printer.Print(resp, *NProtobufJson::CreateJsonMapOutput(value)); } diff --git a/ydb/core/http_proxy/ut/http_proxy_ut.h b/ydb/core/http_proxy/ut/http_proxy_ut.h index 006037e26894..39ea72747ccc 100644 --- a/ydb/core/http_proxy/ut/http_proxy_ut.h +++ b/ydb/core/http_proxy/ut/http_proxy_ut.h @@ -1665,7 +1665,7 @@ Y_UNIT_TEST_SUITE(TestHttpProxy) { Y_UNIT_TEST_F(TestReceiveMessage, THttpProxyTestMock) { auto createQueueReq = CreateSqsCreateQueueRequest(); - auto res = SendHttpRequest("/Root", "AmazonSQS.CreateQueue", std::move(createQueueReq), FormAuthorizationStr("ru-central1")); + auto res = SendHttpRequest("/Root", "AmazonSQS.CreateQueue", createQueueReq, FormAuthorizationStr("ru-central1")); UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); NJson::TJsonValue json; UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); @@ -1674,21 +1674,20 @@ Y_UNIT_TEST_SUITE(TestHttpProxy) { NJson::TJsonValue sendMessageReq; sendMessageReq["QueueUrl"] = resultQueueUrl; - auto body = "MessageBody-0"; - sendMessageReq["MessageBody"] = body; - sendMessageReq["MessageBody"] = body; + auto body0 = "MessageBody-0"; + sendMessageReq["MessageBody"] = body0; - res = SendHttpRequest("/Root", "AmazonSQS.SendMessage", std::move(sendMessageReq), FormAuthorizationStr("ru-central1")); + res = SendHttpRequest("/Root", "AmazonSQS.SendMessage", sendMessageReq, FormAuthorizationStr("ru-central1")); UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); UNIT_ASSERT(!GetByPath(json, "MD5OfMessageBody").empty()); UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; for (int i = 0; i < 20; ++i) { - NJson::TJsonValue receiveMessageReq; - receiveMessageReq["QueueUrl"] = resultQueueUrl; - res = SendHttpRequest("/Root", "AmazonSQS.ReceiveMessage", std::move(receiveMessageReq), FormAuthorizationStr("ru-central1")); - if (res.Body != TString("{}")) { - break;; + res = SendHttpRequest("/Root", "AmazonSQS.ReceiveMessage", receiveMessageReq, FormAuthorizationStr("ru-central1")); + if (res.Body != "{}") { + break; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } @@ -1696,7 +1695,89 @@ Y_UNIT_TEST_SUITE(TestHttpProxy) { UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); UNIT_ASSERT_VALUES_EQUAL(json["Messages"].GetArray().size(), 1); - UNIT_ASSERT_VALUES_EQUAL(json["Messages"][0]["Body"], body); + UNIT_ASSERT_VALUES_EQUAL(json["Messages"][0]["Body"], body0); + } + + Y_UNIT_TEST_F(TestReceiveMessageWithAttributes, THttpProxyTestMock) { + // Test if we process AttributeNames, MessageSystemAttributeNames, MessageAttributeNames correctly. + + auto createQueueReq = CreateSqsCreateQueueRequest(); + auto res = SendHttpRequest("/Root", "AmazonSQS.CreateQueue", createQueueReq, FormAuthorizationStr("ru-central1")); + UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); + NJson::TJsonValue json; + UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); + TString resultQueueUrl = GetByPath(json, "QueueUrl"); + UNIT_ASSERT(resultQueueUrl.EndsWith("ExampleQueueName")); + + auto sendMessage = [this, resultQueueUrl](const TString& body) { + NJson::TJsonValue sendMessageReq; + sendMessageReq["QueueUrl"] = resultQueueUrl; + sendMessageReq["MessageBody"] = body; + + auto res = SendHttpRequest("/Root", "AmazonSQS.SendMessage", sendMessageReq, FormAuthorizationStr("ru-central1")); + NJson::TJsonValue json; + UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); + UNIT_ASSERT(!GetByPath(json, "MD5OfMessageBody").empty()); + UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); + }; + + TString body = "MessageBody-0"; + sendMessage(body); + + auto receiveMessage = [this](NJson::TJsonValue request, const TString& expectedBody) -> NJson::TJsonValue { + request["VisibilityTimeout"] = 0; // Keep the message visible for next ReceiveMessage requests. + THttpResult res; + for (int i = 0; i < 20; ++i) { + res = SendHttpRequest("/Root", "AmazonSQS.ReceiveMessage", request, FormAuthorizationStr("ru-central1")); + if (res.Body != "{}") { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + NJson::TJsonValue json; + UNIT_ASSERT(NJson::ReadJsonTree(res.Body, &json)); + UNIT_ASSERT_VALUES_EQUAL(res.HttpCode, 200); + UNIT_ASSERT_VALUES_EQUAL(json["Messages"].GetArray().size(), 1); + UNIT_ASSERT_VALUES_EQUAL(json["Messages"][0]["Body"], expectedBody); + return json; + }; + + { + // Request SentTimestamp message system attribute using deprecated AttributeNames field. + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; + receiveMessageReq["AttributeNames"] = NJson::TJsonArray{"SentTimestamp"}; + json = receiveMessage(receiveMessageReq, body); + UNIT_ASSERT(!json["Messages"][0]["Attributes"]["SentTimestamp"].GetString().empty()); + } + + { + // Request SentTimestamp message system attribute using MessageSystemAttributeNames field. + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; + receiveMessageReq["MessageSystemAttributeNames"] = NJson::TJsonArray{"SentTimestamp"}; + json = receiveMessage(receiveMessageReq, body); + UNIT_ASSERT(!json["Messages"][0]["Attributes"]["SentTimestamp"].GetString().empty()); + } + + { + // Request All message system attributes using deprecated AttributeNames field. + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; + receiveMessageReq["AttributeNames"] = NJson::TJsonArray{"All"}; + json = receiveMessage(receiveMessageReq, body); + UNIT_ASSERT(!json["Messages"][0]["Attributes"]["SentTimestamp"].GetString().empty()); + } + + { + // Request All message system attributes using MessageSystemAttributeNames field. + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; + receiveMessageReq["MessageSystemAttributeNames"] = NJson::TJsonArray{"All"}; + json = receiveMessage(receiveMessageReq, body); + UNIT_ASSERT(!json["Messages"][0]["Attributes"]["SentTimestamp"].GetString().empty()); + } } Y_UNIT_TEST_F(TestGetQueueAttributes, THttpProxyTestMock) { @@ -1970,6 +2051,10 @@ Y_UNIT_TEST_SUITE(TestHttpProxy) { UNIT_ASSERT(!GetByPath(succesful0, "MD5OfMessageAttributes").empty()); UNIT_ASSERT(!GetByPath(succesful0, "MD5OfMessageBody").empty()); UNIT_ASSERT(!GetByPath(succesful0, "MessageId").empty()); + + NJson::TJsonValue receiveMessageReq; + receiveMessageReq["QueueUrl"] = resultQueueUrl; + res = SendHttpRequest("/Root", "AmazonSQS.ReceiveMessage", std::move(receiveMessageReq), FormAuthorizationStr("ru-central1")); } Y_UNIT_TEST_F(TestDeleteMessageBatch, THttpProxyTestMock) { diff --git a/ydb/core/http_proxy/ut/json_proto_conversion_ut.cpp b/ydb/core/http_proxy/ut/json_proto_conversion_ut.cpp index 3f898280de0b..dd117d0535cf 100644 --- a/ydb/core/http_proxy/ut/json_proto_conversion_ut.cpp +++ b/ydb/core/http_proxy/ut/json_proto_conversion_ut.cpp @@ -202,6 +202,18 @@ Y_UNIT_TEST(ProtoMapToJson) { } } +Y_UNIT_TEST(ProtoMapToJson_ReceiveMessageResult) { + // Test using ReceiveMessageResult that has a repeated field with TRANSFORM_BASE64. + // Before fix it failed on messages with attributes. + { + Ydb::Ymq::V1::ReceiveMessageResult message; + message.add_messages()->mutable_message_attributes()->insert({google::protobuf::MapPair("a", {})}); + + NJson::TJsonValue jsonObject; + NKikimr::NHttpProxy::ProtoToJson(message, jsonObject, false); + } +} + Y_UNIT_TEST(NlohmannJsonToProtoMap) { { nlohmann::json jsonObject; diff --git a/ydb/core/kqp/common/compilation/result.cpp b/ydb/core/kqp/common/compilation/result.cpp index 292cd0d03c4b..02672db8189a 100644 --- a/ydb/core/kqp/common/compilation/result.cpp +++ b/ydb/core/kqp/common/compilation/result.cpp @@ -2,4 +2,11 @@ namespace NKikimr::NKqp { +std::shared_ptr TKqpCompileResult::GetAst() const { + if (QueryAst) { + return QueryAst->Ast; + } + return nullptr; +} + } // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/common/compilation/result.h b/ydb/core/kqp/common/compilation/result.h index e5ab7cfc7d6a..1f74b7f8f045 100644 --- a/ydb/core/kqp/common/compilation/result.h +++ b/ydb/core/kqp/common/compilation/result.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -14,24 +15,26 @@ struct TKqpCompileResult { using TConstPtr = std::shared_ptr; TKqpCompileResult(const TString& uid, const Ydb::StatusIds::StatusCode& status, const NYql::TIssues& issues, - ETableReadType maxReadType, TMaybe query = {}, std::shared_ptr ast = {}, + ETableReadType maxReadType, TMaybe query = {}, TMaybe queryAst = {}, bool needToSplit = false, const TMaybe& commandTagName = {}) : Status(status) , Issues(issues) , Query(std::move(query)) , Uid(uid) , MaxReadType(maxReadType) - , Ast(std::move(ast)) + , QueryAst(std::move(queryAst)) , NeedToSplit(needToSplit) , CommandTagName(commandTagName) {} static std::shared_ptr Make(const TString& uid, const Ydb::StatusIds::StatusCode& status, const NYql::TIssues& issues, ETableReadType maxReadType, TMaybe query = {}, - std::shared_ptr ast = {}, bool needToSplit = false, const TMaybe& commandTagName = {}) + TMaybe queryAst = {}, bool needToSplit = false, const TMaybe& commandTagName = {}) { - return std::make_shared(uid, status, issues, maxReadType, std::move(query), std::move(ast), needToSplit, commandTagName); + return std::make_shared(uid, status, issues, maxReadType, std::move(query), std::move(queryAst), needToSplit, commandTagName); } + std::shared_ptr GetAst() const; + Ydb::StatusIds::StatusCode Status; NYql::TIssues Issues; @@ -40,7 +43,7 @@ struct TKqpCompileResult { ETableReadType MaxReadType; bool AllowCache = true; - std::shared_ptr Ast; + TMaybe QueryAst; bool NeedToSplit = false; TMaybe CommandTagName = {}; diff --git a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp index 58bb175ab894..4079fe093f3f 100644 --- a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp +++ b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp @@ -379,9 +379,9 @@ class TKqpCompileActor : public TActorBootstrapped { void ReplyError(Ydb::StatusIds::StatusCode status, const TIssues& issues) { if (!KqpCompileResult) { - KqpCompileResult = TKqpCompileResult::Make(Uid, status, issues, ETableReadType::Other, std::move(QueryId)); + KqpCompileResult = TKqpCompileResult::Make(Uid, status, issues, ETableReadType::Other, std::move(QueryId), std::move(QueryAst)); } else { - KqpCompileResult = TKqpCompileResult::Make(Uid, status, issues, ETableReadType::Other, std::move(KqpCompileResult->Query)); + KqpCompileResult = TKqpCompileResult::Make(Uid, status, issues, ETableReadType::Other, std::move(KqpCompileResult->Query), std::move(KqpCompileResult->QueryAst)); } Reply(); @@ -456,10 +456,6 @@ class TKqpCompileActor : public TActorBootstrapped { preparedQueryHolder->MutableLlvmSettings().Fill(Config, queryType); KqpCompileResult->PreparedQuery = preparedQueryHolder; KqpCompileResult->AllowCache = CanCacheQuery(KqpCompileResult->PreparedQuery->GetPhysicalQuery()) && allowCache; - - if (QueryAst) { - KqpCompileResult->Ast = QueryAst->Ast; - } } void Handle(TEvKqp::TEvContinueProcess::TPtr &ev, const TActorContext &ctx) { @@ -478,7 +474,7 @@ class TKqpCompileActor : public TActorBootstrapped { if (kqpResult.NeedToSplit) { KqpCompileResult = TKqpCompileResult::Make( - Uid, status, kqpResult.Issues(), ETableReadType::Other, std::move(QueryId), {}, true); + Uid, status, kqpResult.Issues(), ETableReadType::Other, std::move(QueryId), std::move(QueryAst), true); Reply(); return; } @@ -496,7 +492,7 @@ class TKqpCompileActor : public TActorBootstrapped { auto queryType = QueryId.Settings.QueryType; - KqpCompileResult = TKqpCompileResult::Make(Uid, status, kqpResult.Issues(), maxReadType, std::move(QueryId)); + KqpCompileResult = TKqpCompileResult::Make(Uid, status, kqpResult.Issues(), maxReadType, std::move(QueryId), std::move(QueryAst)); KqpCompileResult->CommandTagName = kqpResult.CommandTagName; if (status == Ydb::StatusIds::SUCCESS) { diff --git a/ydb/core/kqp/compile_service/kqp_compile_service.cpp b/ydb/core/kqp/compile_service/kqp_compile_service.cpp index 85f575eedabb..4be278911ccf 100644 --- a/ydb/core/kqp/compile_service/kqp_compile_service.cpp +++ b/ydb/core/kqp/compile_service/kqp_compile_service.cpp @@ -50,16 +50,16 @@ class TKqpQueryCache { void InsertAst(const TKqpCompileResult::TConstPtr& compileResult) { Y_ENSURE(compileResult->Query); - Y_ENSURE(compileResult->Ast); + Y_ENSURE(compileResult->GetAst()); - AstIndex.emplace(GetQueryIdWithAst(*compileResult->Query, *compileResult->Ast), compileResult->Uid); + AstIndex.emplace(GetQueryIdWithAst(*compileResult->Query, *compileResult->GetAst()), compileResult->Uid); } bool Insert(const TKqpCompileResult::TConstPtr& compileResult, bool isEnableAstCache, bool isPerStatementExecution) { if (!isPerStatementExecution) { InsertQuery(compileResult); } - if (isEnableAstCache && compileResult->Ast) { + if (isEnableAstCache && compileResult->GetAst()) { InsertAst(compileResult); } @@ -76,8 +76,8 @@ class TKqpQueryCache { auto queryId = *removedItem->Value.CompileResult->Query; QueryIndex.erase(queryId); - if (removedItem->Value.CompileResult->Ast) { - AstIndex.erase(GetQueryIdWithAst(queryId, *removedItem->Value.CompileResult->Ast)); + if (removedItem->Value.CompileResult->GetAst()) { + AstIndex.erase(GetQueryIdWithAst(queryId, *removedItem->Value.CompileResult->GetAst())); } auto indexIt = Index.find(*removedItem); if (indexIt != Index.end()) { @@ -190,8 +190,8 @@ class TKqpQueryCache { Y_ABORT_UNLESS(item->Value.CompileResult->Query); auto queryId = *item->Value.CompileResult->Query; QueryIndex.erase(queryId); - if (item->Value.CompileResult->Ast) { - AstIndex.erase(GetQueryIdWithAst(queryId, *item->Value.CompileResult->Ast)); + if (item->Value.CompileResult->GetAst()) { + AstIndex.erase(GetQueryIdWithAst(queryId, *item->Value.CompileResult->GetAst())); } Index.erase(it); @@ -327,6 +327,8 @@ struct TKqpCompileRequest { NYql::TExprContext* SplitCtx; NYql::TExprNode::TPtr SplitExpr; + bool FindInCache = true; + bool IsIntrestedInResult() const { return IntrestedInResult->load(); } @@ -771,8 +773,6 @@ class TKqpCompileService : public TActorBootstrapped { } if (compileResult || request.Query) { - QueryCache.EraseByUid(request.Uid); - Counters->ReportCompileRequestCompile(dbCounters); NWilson::TSpan compileServiceSpan(TWilsonKqp::CompileService, ev->Get() ? std::move(ev->TraceId) : NWilson::TTraceId(), "CompileService"); @@ -797,12 +797,13 @@ class TKqpCompileService : public TActorBootstrapped { ); } } - TKqpCompileRequest compileRequest(ev->Sender, request.Uid, query, + TKqpCompileRequest compileRequest(ev->Sender, request.Uid, compileResult ? *compileResult->Query : *request.Query, compileSettings, request.UserToken, dbCounters, request.GUCSettings, request.ApplicationName, ev->Cookie, std::move(ev->Get()->IntrestedInResult), ev->Get()->UserRequestContext, ev->Get() ? std::move(ev->Get()->Orbit) : NLWTrace::TOrbit(), std::move(compileServiceSpan), std::move(ev->Get()->TempTablesState)); + compileRequest.FindInCache = false; if (TableServiceConfig.GetEnableAstCache() && request.QueryAst) { return CompileByAst(*request.QueryAst, compileRequest, ctx); @@ -983,7 +984,7 @@ class TKqpCompileService : public TActorBootstrapped { << ", ast: " << queryAst.Ast->Root->ToString()); auto compileResult = QueryCache.FindByAst(compileRequest.Query, *queryAst.Ast, compileRequest.CompileSettings.KeepInCache); - if (HasTempTablesNameClashes(compileResult, compileRequest.TempTablesState)) { + if (!compileRequest.FindInCache || HasTempTablesNameClashes(compileResult, compileRequest.TempTablesState)) { compileResult = nullptr; } @@ -994,7 +995,7 @@ class TKqpCompileService : public TActorBootstrapped { << ", sender: " << compileRequest.Sender << ", queryUid: " << compileResult->Uid); - compileResult->Ast->PgAutoParamValues = std::move(queryAst.Ast->PgAutoParamValues); + compileResult->GetAst()->PgAutoParamValues = std::move(queryAst.Ast->PgAutoParamValues); ReplyFromCache(compileRequest.Sender, compileResult, ctx, compileRequest.Cookie, std::move(compileRequest.Orbit), std::move(compileRequest.CompileServiceSpan)); return; @@ -1068,10 +1069,10 @@ class TKqpCompileService : public TActorBootstrapped { if (QueryCache.FindByQuery(query, keepInCache)) { return false; } - if (compileResult->Ast && QueryCache.FindByAst(query, *compileResult->Ast, keepInCache)) { + if (compileResult->GetAst() && QueryCache.FindByAst(query, *compileResult->GetAst(), keepInCache)) { return false; } - auto newCompileResult = TKqpCompileResult::Make(CreateGuidAsString(), compileResult->Status, compileResult->Issues, compileResult->MaxReadType, std::move(query), compileResult->Ast); + auto newCompileResult = TKqpCompileResult::Make(CreateGuidAsString(), compileResult->Status, compileResult->Issues, compileResult->MaxReadType, std::move(query), compileResult->QueryAst); newCompileResult->AllowCache = compileResult->AllowCache; newCompileResult->PreparedQuery = compileResult->PreparedQuery; LOG_DEBUG_S(ctx, NKikimrServices::KQP_COMPILE_SERVICE, "Insert preparing query with params, queryId: " << query.SerializeToString()); diff --git a/ydb/core/kqp/gateway/behaviour/view/manager.cpp b/ydb/core/kqp/gateway/behaviour/view/manager.cpp index 236125c0e85a..069697d53baf 100644 --- a/ydb/core/kqp/gateway/behaviour/view/manager.cpp +++ b/ydb/core/kqp/gateway/behaviour/view/manager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace NKikimr::NKqp { @@ -11,13 +12,9 @@ namespace { using TYqlConclusionStatus = TViewManager::TYqlConclusionStatus; using TInternalModificationContext = TViewManager::TInternalModificationContext; +using TExternalModificationContext = TViewManager::TExternalModificationContext; -TString GetByKeyOrDefault(const NYql::TCreateObjectSettings& container, const TString& key) { - const auto value = container.GetFeaturesExtractor().Extract(key); - return value ? *value : TString{}; -} - -TYqlConclusionStatus CheckFeatureFlag(TInternalModificationContext& context) { +TYqlConclusionStatus CheckFeatureFlag(const TInternalModificationContext& context) { auto* const actorSystem = context.GetExternalData().GetActorSystem(); if (!actorSystem) { ythrow yexception() << "This place needs an actor system. Please contact internal support"; @@ -46,21 +43,32 @@ std::pair SplitPathByObjectId(const TString& objectId) { return pathPair; } +void ValidateOptions(NYql::TFeaturesExtractor& features) { + // Current implementation does not persist the security_invoker option value. + if (features.Extract("security_invoker") != "true") { + ythrow TBadArgumentException() << "security_invoker option must be explicitly enabled"; + } + if (!features.IsFinished()) { + ythrow TBadArgumentException() << "Unknown property: " << features.GetRemainedParamsString(); + } +} + void FillCreateViewProposal(NKikimrSchemeOp::TModifyScheme& modifyScheme, const NYql::TCreateObjectSettings& settings, - const TString& database) { + const TExternalModificationContext& context) { - const auto pathPair = SplitPathByDb(settings.GetObjectId(), database); + const auto pathPair = SplitPathByDb(settings.GetObjectId(), context.GetDatabase()); modifyScheme.SetWorkingDir(pathPair.first); modifyScheme.SetOperationType(NKikimrSchemeOp::ESchemeOpCreateView); auto& viewDesc = *modifyScheme.MutableCreateView(); viewDesc.SetName(pathPair.second); - viewDesc.SetQueryText(GetByKeyOrDefault(settings, "query_text")); - if (!settings.GetFeaturesExtractor().IsFinished()) { - ythrow TBadArgumentException() << "Unknown property: " << settings.GetFeaturesExtractor().GetRemainedParamsString(); - } + auto& features = settings.GetFeaturesExtractor(); + viewDesc.SetQueryText(features.Extract("query_text").value_or("")); + ValidateOptions(features); + + NSQLTranslation::Serialize(context.GetTranslationSettings(), *viewDesc.MutableCapturedContext()); } void FillDropViewProposal(NKikimrSchemeOp::TModifyScheme& modifyScheme, @@ -92,20 +100,20 @@ NThreading::TFuture SendSchemeRequest(TEvTxUserProxy::TEvP } NThreading::TFuture CreateView(const NYql::TCreateObjectSettings& settings, - TInternalModificationContext& context) { + const TInternalModificationContext& context) { auto proposal = MakeHolder(); proposal->Record.SetDatabaseName(context.GetExternalData().GetDatabase()); if (context.GetExternalData().GetUserToken()) { proposal->Record.SetUserToken(context.GetExternalData().GetUserToken()->GetSerializedToken()); } auto& schemeTx = *proposal->Record.MutableTransaction()->MutableModifyScheme(); - FillCreateViewProposal(schemeTx, settings, context.GetExternalData().GetDatabase()); + FillCreateViewProposal(schemeTx, settings, context.GetExternalData()); return SendSchemeRequest(proposal.Release(), context.GetExternalData().GetActorSystem(), true); } NThreading::TFuture DropView(const NYql::TDropObjectSettings& settings, - TInternalModificationContext& context) { + const TInternalModificationContext& context) { auto proposal = MakeHolder(); proposal->Record.SetDatabaseName(context.GetExternalData().GetDatabase()); if (context.GetExternalData().GetUserToken()) { @@ -119,8 +127,8 @@ NThreading::TFuture DropView(const NYql::TDropObjectSettin void PrepareCreateView(NKqpProto::TKqpSchemeOperation& schemeOperation, const NYql::TObjectSettingsImpl& settings, - TInternalModificationContext& context) { - FillCreateViewProposal(*schemeOperation.MutableCreateView(), settings, context.GetExternalData().GetDatabase()); + const TInternalModificationContext& context) { + FillCreateViewProposal(*schemeOperation.MutableCreateView(), settings, context.GetExternalData()); } void PrepareDropView(NKqpProto::TKqpSchemeOperation& schemeOperation, diff --git a/ydb/core/kqp/gateway/behaviour/view/ya.make b/ydb/core/kqp/gateway/behaviour/view/ya.make index 6cb342036bda..7d57b8ceaab8 100644 --- a/ydb/core/kqp/gateway/behaviour/view/ya.make +++ b/ydb/core/kqp/gateway/behaviour/view/ya.make @@ -8,6 +8,7 @@ SRCS( PEERDIR( ydb/core/base ydb/core/kqp/gateway/actors + ydb/core/kqp/provider ydb/core/tx/tx_proxy ydb/services/metadata/abstract ydb/services/metadata/manager diff --git a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp index 7251b76f084e..fc9a3ac619c4 100644 --- a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp +++ b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp @@ -302,7 +302,7 @@ TTableMetadataResult GetViewMetadataResult( metadata->SchemaVersion = description.GetVersion(); metadata->Kind = NYql::EKikimrTableKind::View; metadata->Attributes = schemeEntry.Attributes; - metadata->ViewPersistedData = {description.GetQueryText()}; + metadata->ViewPersistedData = {description.GetQueryText(), description.GetCapturedContext()}; return builtResult; } diff --git a/ydb/core/kqp/host/kqp_gateway_proxy.cpp b/ydb/core/kqp/host/kqp_gateway_proxy.cpp index e571bf031259..a5c89dad6a46 100644 --- a/ydb/core/kqp/host/kqp_gateway_proxy.cpp +++ b/ydb/core/kqp/host/kqp_gateway_proxy.cpp @@ -1266,6 +1266,7 @@ class TKqpGatewayProxy : public IKikimrGateway { if (SessionCtx->GetUserToken()) { context.SetUserToken(*SessionCtx->GetUserToken()); } + context.SetTranslationSettings(SessionCtx->Query().TranslationSettings); auto& phyTx = phyTxRemover.Capture(SessionCtx->Query().PreparingQuery->MutablePhysicalQuery()); phyTx.SetType(NKqpProto::TKqpPhyTx::TYPE_SCHEME); diff --git a/ydb/core/kqp/host/kqp_host.cpp b/ydb/core/kqp/host/kqp_host.cpp index 7a7f1765c8ab..4e5820d45087 100644 --- a/ydb/core/kqp/host/kqp_host.cpp +++ b/ydb/core/kqp/host/kqp_host.cpp @@ -1230,7 +1230,19 @@ class TKqpHost : public IKqpHost { .SetQueryParameters(query.ParameterTypes) .SetApplicationName(ApplicationName) .SetIsEnablePgSyntax(SessionCtx->Config().FeatureFlags.GetEnablePgSyntax()); - auto astRes = ParseQuery(query.Text, isSql, sqlVersion, TypesCtx->DeprecatedSQL, ctx, settingsBuilder, result.KeepInCache, result.CommandTagName); + NSQLTranslation::TTranslationSettings effectiveSettings; + auto astRes = ParseQuery( + query.Text, + isSql, + sqlVersion, + TypesCtx->DeprecatedSQL, + ctx, + settingsBuilder, + result.KeepInCache, + result.CommandTagName, + &effectiveSettings + ); + SessionCtx->Query().TranslationSettings = std::move(effectiveSettings); if (astRes.ActualSyntaxType == NYql::ESyntaxType::Pg) { SessionCtx->Config().IndexAutoChooserMode = NKikimrConfig::TTableServiceConfig_EIndexAutoChooseMode::TTableServiceConfig_EIndexAutoChooseMode_MAX_USED_PREFIX; } diff --git a/ydb/core/kqp/host/kqp_translate.cpp b/ydb/core/kqp/host/kqp_translate.cpp index 8607f5d87d36..8b7d842f88ed 100644 --- a/ydb/core/kqp/host/kqp_translate.cpp +++ b/ydb/core/kqp/host/kqp_translate.cpp @@ -54,7 +54,7 @@ NYql::EKikimrQueryType ConvertType(NKikimrKqp::EQueryType type) { YQL_ENSURE(false, "Unexpected query type: " << type); } } - + NSQLTranslation::TTranslationSettings TKqpTranslationSettingsBuilder::Build(NYql::TExprContext& ctx) { NSQLTranslation::TTranslationSettings settings; settings.PgParser = UsePgParser && *UsePgParser; @@ -154,13 +154,14 @@ NSQLTranslation::TTranslationSettings TKqpTranslationSettingsBuilder::Build(NYql } NYql::TAstParseResult ParseQuery(const TString& queryText, bool isSql, TMaybe& sqlVersion, bool& deprecatedSQL, - NYql::TExprContext& ctx, TKqpTranslationSettingsBuilder& settingsBuilder, bool& keepInCache, TMaybe& commandTagName) { + NYql::TExprContext& ctx, TKqpTranslationSettingsBuilder& settingsBuilder, bool& keepInCache, TMaybe& commandTagName, + NSQLTranslation::TTranslationSettings* effectiveSettings) { NYql::TAstParseResult astRes; settingsBuilder.SetSqlVersion(sqlVersion); if (isSql) { auto settings = settingsBuilder.Build(ctx); NYql::TStmtParseInfo stmtParseInfo; - auto ast = NSQLTranslation::SqlToYql(queryText, settings, nullptr, &stmtParseInfo); + auto ast = NSQLTranslation::SqlToYql(queryText, settings, nullptr, &stmtParseInfo, effectiveSettings); deprecatedSQL = (ast.ActualSyntaxType == NYql::ESyntaxType::YQLv0); sqlVersion = ast.ActualSyntaxType == NYql::ESyntaxType::YQLv1 ? 1 : 0; keepInCache = stmtParseInfo.KeepInCache; diff --git a/ydb/core/kqp/host/kqp_translate.h b/ydb/core/kqp/host/kqp_translate.h index 033a79e166e3..d71c01d4ce2b 100644 --- a/ydb/core/kqp/host/kqp_translate.h +++ b/ydb/core/kqp/host/kqp_translate.h @@ -91,7 +91,8 @@ NSQLTranslation::EBindingsMode RemapBindingsMode(NKikimrConfig::TTableServiceCon NYql::EKikimrQueryType ConvertType(NKikimrKqp::EQueryType type); NYql::TAstParseResult ParseQuery(const TString& queryText, bool isSql, TMaybe& sqlVersion, bool& deprecatedSQL, - NYql::TExprContext& ctx, TKqpTranslationSettingsBuilder& settingsBuilder, bool& keepInCache, TMaybe& commandTagName); + NYql::TExprContext& ctx, TKqpTranslationSettingsBuilder& settingsBuilder, bool& keepInCache, TMaybe& commandTagName, + NSQLTranslation::TTranslationSettings* effectiveSettings = nullptr); TVector ParseStatements(const TString& queryText, const TMaybe& syntax, bool isSql, TKqpTranslationSettingsBuilder& settingsBuilder, bool perStatementExecution); diff --git a/ydb/core/kqp/provider/rewrite_io_utils.cpp b/ydb/core/kqp/provider/rewrite_io_utils.cpp index 9a2bd768904b..067ab4b583a9 100644 --- a/ydb/core/kqp/provider/rewrite_io_utils.cpp +++ b/ydb/core/kqp/provider/rewrite_io_utils.cpp @@ -1,6 +1,7 @@ #include "rewrite_io_utils.h" #include +#include #include #include #include @@ -16,16 +17,17 @@ using namespace NNodes; constexpr const char* QueryGraphNodeSignature = "SavedQueryGraph"; TExprNode::TPtr CompileViewQuery( - const TString& query, TExprContext& ctx, NKikimr::NKqp::TKqpTranslationSettingsBuilder& settingsBuilder, - IModuleResolver::TPtr moduleResolver + IModuleResolver::TPtr moduleResolver, + const TViewPersistedData& viewData ) { auto translationSettings = settingsBuilder.Build(ctx); translationSettings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW; + NSQLTranslation::Deserialize(viewData.CapturedContext, translationSettings); TAstParseResult queryAst; - queryAst = NSQLTranslation::SqlToYql(query, translationSettings); + queryAst = NSQLTranslation::SqlToYql(viewData.QueryText, translationSettings); ctx.IssueManager.AddIssues(queryAst.Issues); if (!queryAst.IsOk()) { @@ -116,9 +118,9 @@ TExprNode::TPtr FindTopLevelRead(const TExprNode::TPtr& queryGraph) { TExprNode::TPtr RewriteReadFromView( const TExprNode::TPtr& node, TExprContext& ctx, - const TString& query, NKikimr::NKqp::TKqpTranslationSettingsBuilder& settingsBuilder, - IModuleResolver::TPtr moduleResolver + IModuleResolver::TPtr moduleResolver, + const TViewPersistedData& viewData ) { YQL_PROFILE_FUNC(DEBUG); @@ -127,7 +129,7 @@ TExprNode::TPtr RewriteReadFromView( TExprNode::TPtr queryGraph = FindSavedQueryGraph(readNode.Ptr()); if (!queryGraph) { - queryGraph = CompileViewQuery(query, ctx, settingsBuilder, moduleResolver); + queryGraph = CompileViewQuery(ctx, settingsBuilder, moduleResolver, viewData); if (!queryGraph) { ctx.AddError(TIssue(ctx.GetPosition(readNode.Pos()), "The query stored in the view cannot be compiled.")); @@ -151,4 +153,4 @@ TExprNode::TPtr RewriteReadFromView( return Build(ctx, node->Pos()).Input(topLevelRead).Done().Ptr(); } -} \ No newline at end of file +} diff --git a/ydb/core/kqp/provider/rewrite_io_utils.h b/ydb/core/kqp/provider/rewrite_io_utils.h index 71beb4e56c22..dd3dff66dd06 100644 --- a/ydb/core/kqp/provider/rewrite_io_utils.h +++ b/ydb/core/kqp/provider/rewrite_io_utils.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace NYql { @@ -10,9 +11,9 @@ TExprNode::TPtr FindTopLevelRead(const TExprNode::TPtr& queryGraph); TExprNode::TPtr RewriteReadFromView( const TExprNode::TPtr& node, TExprContext& ctx, - const TString& query, NKikimr::NKqp::TKqpTranslationSettingsBuilder& settingsBuilder, - IModuleResolver::TPtr moduleResolver + IModuleResolver::TPtr moduleResolver, + const TViewPersistedData& viewData ); -} \ No newline at end of file +} diff --git a/ydb/core/kqp/provider/yql_kikimr_datasink.cpp b/ydb/core/kqp/provider/yql_kikimr_datasink.cpp index 6d1c9fe05600..e36f8ce93841 100644 --- a/ydb/core/kqp/provider/yql_kikimr_datasink.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_datasink.cpp @@ -186,9 +186,9 @@ class TKiSinkIntentDeterminationTransformer: public TKiSinkVisitorTransformer { } TStatus HandleDropObject(TKiDropObject node, TExprContext& ctx) override { - ctx.AddError(TIssue(ctx.GetPosition(node.Pos()), TStringBuilder() - << "DropObject is not yet implemented for intent determination transformer")); - return TStatus::Error; + Y_UNUSED(node); + Y_UNUSED(ctx); + return TStatus::Ok; } TStatus HandleCreateGroup(TKiCreateGroup node, TExprContext& ctx) override { diff --git a/ydb/core/kqp/provider/yql_kikimr_datasource.cpp b/ydb/core/kqp/provider/yql_kikimr_datasource.cpp index 3a0ba86838f7..1c3a02efc504 100644 --- a/ydb/core/kqp/provider/yql_kikimr_datasource.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_datasource.cpp @@ -771,16 +771,17 @@ class TKikimrDataSource : public TDataProviderBase { .Repeat(TExprStep::LoadTablesMetadata) .Repeat(TExprStep::RewriteIO); - const auto& query = tableDesc.Metadata->ViewPersistedData.QueryText; + const auto& viewData = tableDesc.Metadata->ViewPersistedData; + NKqp::TKqpTranslationSettingsBuilder settingsBuilder( SessionCtx->Query().Type, SessionCtx->Config()._KqpYqlSyntaxVersion.Get().GetRef(), cluster, - query, + viewData.QueryText, SessionCtx->Config().BindingsMode, GUCSettings ); - return RewriteReadFromView(node, ctx, query, settingsBuilder, Types.Modules); + return RewriteReadFromView(node, ctx, settingsBuilder, Types.Modules, viewData); } } diff --git a/ydb/core/kqp/provider/yql_kikimr_gateway.h b/ydb/core/kqp/provider/yql_kikimr_gateway.h index 6406be3095ff..93885eac7563 100644 --- a/ydb/core/kqp/provider/yql_kikimr_gateway.h +++ b/ydb/core/kqp/provider/yql_kikimr_gateway.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -406,6 +407,7 @@ enum EMetaSerializationType : ui64 { struct TViewPersistedData { TString QueryText; + NYql::NProto::TTranslationSettings CapturedContext; }; struct TKikimrTableMetadata : public TThrRefBase { diff --git a/ydb/core/kqp/provider/yql_kikimr_provider.cpp b/ydb/core/kqp/provider/yql_kikimr_provider.cpp index bfeba1dd97cb..42fd3b749594 100644 --- a/ydb/core/kqp/provider/yql_kikimr_provider.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_provider.cpp @@ -965,3 +965,36 @@ TCoNameValueTupleList TKiExecDataQuerySettings::BuildNode(TExprContext& ctx, TPo } } // namespace NYql + +namespace NSQLTranslation { + +void Serialize(const TTranslationSettings& settings, NYql::NProto::TTranslationSettings& serializedSettings) { + serializedSettings.SetPathPrefix(settings.PathPrefix); + serializedSettings.SetSyntaxVersion(settings.SyntaxVersion); + serializedSettings.SetAnsiLexer(settings.AnsiLexer); + serializedSettings.SetPgParser(settings.PgParser); + + auto* pragmas = serializedSettings.MutablePragmas(); + pragmas->Clear(); + pragmas->Add(settings.Flags.begin(), settings.Flags.end()); +} + +void Deserialize(const NYql::NProto::TTranslationSettings& serializedSettings, TTranslationSettings& settings) { + #define DeserializeSetting(settingName) \ + if (serializedSettings.Has##settingName()) { \ + settings.settingName = serializedSettings.Get##settingName(); \ + } + + DeserializeSetting(PathPrefix); + DeserializeSetting(SyntaxVersion); + DeserializeSetting(AnsiLexer); + DeserializeSetting(PgParser); + + #undef DeserializeSetting + + // overwrite existing pragmas + settings.Flags.clear(); + settings.Flags.insert(serializedSettings.GetPragmas().begin(), serializedSettings.GetPragmas().end()); +} + +} diff --git a/ydb/core/kqp/provider/yql_kikimr_provider.h b/ydb/core/kqp/provider/yql_kikimr_provider.h index 577afdd8177d..003dd61a6295 100644 --- a/ydb/core/kqp/provider/yql_kikimr_provider.h +++ b/ydb/core/kqp/provider/yql_kikimr_provider.h @@ -125,6 +125,8 @@ struct TKikimrQueryContext : TThrRefBase { // we do not want add extra life time for query context here std::shared_ptr RpcCtx; + NSQLTranslation::TTranslationSettings TranslationSettings; + void Reset() { PrepareOnly = false; SuppressDdlChecks = false; @@ -143,6 +145,7 @@ struct TKikimrQueryContext : TThrRefBase { RlPath.Clear(); RpcCtx.reset(); + TranslationSettings = NSQLTranslation::TTranslationSettings(); } }; @@ -587,3 +590,10 @@ TIntrusivePtr CreateKikimrDataSink( TIntrusivePtr queryExecutor); } // namespace NYql + +namespace NSQLTranslation { + +void Serialize(const TTranslationSettings& settings, NYql::NProto::TTranslationSettings& serializedSettings); +void Deserialize(const NYql::NProto::TTranslationSettings& serializedSettings, TTranslationSettings& settings); + +} diff --git a/ydb/core/kqp/session_actor/kqp_query_state.cpp b/ydb/core/kqp/session_actor/kqp_query_state.cpp index 7aa59a3909e2..f45f214b044f 100644 --- a/ydb/core/kqp/session_actor/kqp_query_state.cpp +++ b/ydb/core/kqp/session_actor/kqp_query_state.cpp @@ -161,7 +161,7 @@ bool TKqpQueryState::SaveAndCheckCompileResult(TEvKqp::TEvCompileResponse* ev) { CommandTagName = CompileResult->CommandTagName; } for (const auto& param : PreparedQuery->GetParameters()) { - const auto& ast = CompileResult->Ast; + const auto& ast = CompileResult->GetAst(); if (!ast || !ast->PgAutoParamValues || !ast->PgAutoParamValues->contains(param.GetName())) { ResultParams.push_back(param); } @@ -276,15 +276,9 @@ std::unique_ptr TKqpQueryState::BuildReCompileReque compileDeadline = Min(compileDeadline, QueryDeadlines.CancelAt); } - TMaybe statementAst; - if (!Statements.empty()) { - YQL_ENSURE(CurrentStatementId < Statements.size()); - statementAst = Statements[CurrentStatementId]; - } - return std::make_unique(UserToken, CompileResult->Uid, query, isQueryActionPrepare, compileDeadline, DbCounters, gUCSettingsPtr, ApplicationName, std::move(cookie), UserRequestContext, std::move(Orbit), TempTablesState, - statementAst); + CompileResult->QueryAst); } std::unique_ptr TKqpQueryState::BuildSplitRequest(std::shared_ptr> cookie, const TGUCSettings::TPtr& gUCSettingsPtr) { diff --git a/ydb/core/kqp/session_actor/kqp_session_actor.cpp b/ydb/core/kqp/session_actor/kqp_session_actor.cpp index b49191c7aa62..5445cfe339a0 100644 --- a/ydb/core/kqp/session_actor/kqp_session_actor.cpp +++ b/ydb/core/kqp/session_actor/kqp_session_actor.cpp @@ -900,8 +900,8 @@ class TKqpSessionActor : public TActorBootstrapped { try { const auto& parameters = QueryState->GetYdbParameters(); QueryState->QueryData->ParseParameters(parameters); - if (QueryState->CompileResult && QueryState->CompileResult->Ast && QueryState->CompileResult->Ast->PgAutoParamValues) { - for(const auto& [name, param] : *QueryState->CompileResult->Ast->PgAutoParamValues) { + if (QueryState->CompileResult && QueryState->CompileResult->GetAst() && QueryState->CompileResult->GetAst()->PgAutoParamValues) { + for(const auto& [name, param] : *QueryState->CompileResult->GetAst()->PgAutoParamValues) { if (!parameters.contains(name)) { QueryState->QueryData->AddTypedValueParam(name, param); } diff --git a/ydb/core/kqp/ut/common/kqp_ut_common.cpp b/ydb/core/kqp/ut/common/kqp_ut_common.cpp index 1cfd1d07c294..84fbe3585652 100644 --- a/ydb/core/kqp/ut/common/kqp_ut_common.cpp +++ b/ydb/core/kqp/ut/common/kqp_ut_common.cpp @@ -129,7 +129,6 @@ TKikimrRunner::TKikimrRunner(const TKikimrSettings& settings) { ServerSettings->SetFrFactory(&UdfFrFactory); ServerSettings->SetEnableNotNullColumns(true); ServerSettings->SetEnableMoveIndex(true); - ServerSettings->SetEnableUniqConstraint(true); ServerSettings->SetUseRealThreads(settings.UseRealThreads); ServerSettings->SetEnableTablePgTypes(true); ServerSettings->SetEnablePgSyntax(true); diff --git a/ydb/core/kqp/ut/view/view_ut.cpp b/ydb/core/kqp/ut/view/view_ut.cpp index 056a16b7958f..111c2faee84f 100644 --- a/ydb/core/kqp/ut/view/view_ut.cpp +++ b/ydb/core/kqp/ut/view/view_ut.cpp @@ -56,6 +56,20 @@ TString ReadWholeFile(const TString& path) { return file.ReadAll(); } +NQuery::TExecuteQueryResult ExecuteQuery(NQuery::TSession& session, const TString& query) { + const auto result = session.ExecuteQuery( + query, + NQuery::TTxControl::NoTx() + ).ExtractValueSync(); + + UNIT_ASSERT_C(result.IsSuccess(), + "Failed to execute the following query:\n" << query << '\n' + << "The issues:\n" << result.GetIssues().ToString() + ); + + return result; +} + void ExecuteDataDefinitionQuery(TSession& session, const TString& script) { const auto result = session.ExecuteSchemeQuery(script).ExtractValueSync(); UNIT_ASSERT_C(result.IsSuccess(), "Failed to execute the following DDL script:\n" @@ -110,16 +124,21 @@ void AssertFromCache(const TMaybe& stats, bool expectedValue) { UNIT_ASSERT_VALUES_EQUAL_C(*isFromCache, expectedValue, stats->ToString()); } -void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) { - const auto& firstResults = first.GetResultSets(); - const auto& secondResults = second.GetResultSets(); - +void CompareResults(const TVector& firstResults, const TVector& secondResults) { UNIT_ASSERT_VALUES_EQUAL(firstResults.size(), secondResults.size()); for (size_t i = 0; i < firstResults.size(); ++i) { CompareYson(FormatResultSetYson(firstResults[i]), FormatResultSetYson(secondResults[i])); } } +void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) { + CompareResults(first.GetResultSets(), second.GetResultSets()); +} + +void CompareResults(const NQuery::TExecuteQueryResult& first, const NQuery::TExecuteQueryResult& second) { + CompareResults(first.GetResultSets(), second.GetResultSets()); +} + void InitializeTablesAndSecondaryViews(TSession& session) { const auto inputFolder = ArcadiaFromCurrentLocation(__SOURCE_FILE__, "input"); ExecuteDataDefinitionQuery(session, ReadWholeFile(inputFolder + "/create_tables_and_secondary_views.sql")); @@ -194,6 +213,90 @@ Y_UNIT_TEST_SUITE(TCreateAndDropViewTest) { UNIT_ASSERT_STRING_CONTAINS(creationResult.GetIssues().ToString(), "Error: Cannot divide type String and String"); } + Y_UNIT_TEST(ParsingSecurityInvoker) { + TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false)); + EnableViewsFeatureFlag(kikimr); + auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession(); + + constexpr const char* path = "TheView"; + constexpr const char* query = "SELECT 1"; + + auto fail = [&](const char* options) { + const TString creationQuery = std::format(R"( + CREATE VIEW {} {} AS {}; + )", + path, + options, + query + ); + + const auto creationResult = session.ExecuteQuery( + creationQuery, + NQuery::TTxControl::NoTx() + ).ExtractValueSync(); + + UNIT_ASSERT_C(!creationResult.IsSuccess(), creationQuery); + UNIT_ASSERT_STRING_CONTAINS( + creationResult.GetIssues().ToString(), "security_invoker option must be explicitly enabled" + ); + }; + fail(""); + fail("WITH security_invoker"); + fail("WITH security_invoker = false"); + fail("WITH SECURITY_INVOKER = true"); // option name is case-sensitive + fail("WITH (security_invoker)"); + fail("WITH (security_invoker = false)"); + fail("WITH (security_invoker = true, security_invoker = false)"); + + auto succeed = [&](const char* options) { + const TString creationQuery = std::format(R"( + CREATE VIEW {} {} AS {}; + DROP VIEW {}; + )", + path, + options, + query, + path + ); + ExecuteQuery(session, creationQuery); + }; + succeed("WITH security_invoker = true"); + succeed("WITH (security_invoker = true)"); + succeed("WITH (security_invoker = tRuE)"); // bool parsing is flexible enough + succeed("WITH (security_invoker = false, security_invoker = true)"); + + { + // literal named expression + const TString creationQuery = std::format(R"( + $value = "true"; + CREATE VIEW {} WITH security_invoker = $value AS {}; + DROP VIEW {}; + )", + path, + query, + path + ); + ExecuteQuery(session, creationQuery); + } + { + // evaluated expression + const TString creationQuery = std::format(R"( + $lambda = ($x) -> {{ + RETURN CAST($x as String) + }}; + $value = $lambda(true); + + CREATE VIEW {} WITH security_invoker = $value AS {}; + DROP VIEW {}; + )", + path, + query, + path + ); + ExecuteQuery(session, creationQuery); + } + } + Y_UNIT_TEST(ListCreatedView) { TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false)); EnableViewsFeatureFlag(kikimr); @@ -408,6 +511,46 @@ Y_UNIT_TEST_SUITE(TSelectFromViewTest) { CompareResults(etalonResults, selectFromViewResults); } + Y_UNIT_TEST(OneTableUsingRelativeName) { + TKikimrRunner kikimr; + + auto& runtime = *kikimr.GetTestServer().GetRuntime(); + runtime.SetLogPriority(NKikimrServices::FLAT_TX_SCHEMESHARD, NLog::PRI_DEBUG); + + EnableViewsFeatureFlag(kikimr); + auto session = kikimr.GetQueryClient().GetSession().ExtractValueSync().GetSession(); + + constexpr const char* viewName = "TheView"; + constexpr const char* testTable = "Test"; + const auto innerQuery = std::format(R"( + SELECT * FROM {} + )", + testTable + ); + + const TString creationQuery = std::format(R"( + CREATE VIEW {} WITH (security_invoker = true) AS {}; + )", + viewName, + innerQuery + ); + ExecuteQuery(session, creationQuery); + + const auto etalonResults = ExecuteQuery(session, std::format(R"( + SELECT * FROM ({}); + )", + innerQuery + ) + ); + const auto selectFromViewResults = ExecuteQuery(session, std::format(R"( + SELECT * FROM {}; + )", + viewName + ) + ); + CompareResults(etalonResults, selectFromViewResults); + } + Y_UNIT_TEST(DisabledFeatureFlag) { TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false)); auto session = kikimr.GetTableClient().CreateSession().GetValueSync().GetSession(); diff --git a/ydb/core/mind/dynamic_nameserver.cpp b/ydb/core/mind/dynamic_nameserver.cpp index 9f31734f3c67..d6d135286404 100644 --- a/ydb/core/mind/dynamic_nameserver.cpp +++ b/ydb/core/mind/dynamic_nameserver.cpp @@ -286,6 +286,7 @@ void TDynamicNameserver::UpdateState(const NKikimrNodeBroker::TNodesInfo &rec, ctx.Schedule(config->Epoch.End - ctx.Now(), new TEvPrivate::TEvUpdateEpoch(domain, config->Epoch.Id + 1)); } else { + // Note: this update may be optimized to only include new nodes for (auto &node : rec.GetNodes()) { auto nodeId = node.GetNodeId(); if (!config->DynamicNodes.contains(nodeId)) diff --git a/ydb/core/mind/node_broker.cpp b/ydb/core/mind/node_broker.cpp index 63fd3b5a2ede..feb19c94e374 100644 --- a/ydb/core/mind/node_broker.cpp +++ b/ydb/core/mind/node_broker.cpp @@ -290,14 +290,45 @@ void TNodeBroker::AddDelayedListNodesRequest(ui64 epoch, void TNodeBroker::ProcessListNodesRequest(TEvNodeBroker::TEvListNodes::TPtr &ev) { - ui64 version = ev->Get()->Record.GetCachedVersion(); + auto *msg = ev->Get(); NKikimrNodeBroker::TNodesInfo info; Epoch.Serialize(*info.MutableEpoch()); info.SetDomain(AppData()->DomainsInfo->GetDomain()->DomainUid); TAutoPtr resp = new TEvNodeBroker::TEvNodesInfo(info); - if (version != Epoch.Version) + + bool optimized = false; + + if (msg->Record.HasCachedVersion()) { + if (msg->Record.GetCachedVersion() == Epoch.Version) { + // Client has an up-to-date list already + optimized = true; + } else { + // We may be able to only send added nodes in the same epoch when + // all deltas are cached up to the current epoch inclusive. + ui64 neededFirstVersion = msg->Record.GetCachedVersion() + 1; + if (!EpochDeltasVersions.empty() && + EpochDeltasVersions.front() <= neededFirstVersion && + EpochDeltasVersions.back() == Epoch.Version && + neededFirstVersion <= Epoch.Version) + { + ui64 firstIndex = neededFirstVersion - EpochDeltasVersions.front(); + if (firstIndex > 0) { + // Note: usually there is a small number of nodes added + // between subsequent requests, so this substr should be + // very cheap. + resp->PreSerializedData = EpochDeltasCache.substr(EpochDeltasEndOffsets[firstIndex - 1]); + } else { + resp->PreSerializedData = EpochDeltasCache; + } + optimized = true; + } + } + } + + if (!optimized) { resp->PreSerializedData = EpochCache; + } TabletCounters->Percentile()[COUNTER_LIST_NODES_BYTES].IncrementFor(resp->GetCachedByteSize()); LOG_TRACE_S(TActorContext::AsActorContext(), NKikimrServices::NODE_BROKER, @@ -308,12 +339,16 @@ void TNodeBroker::ProcessListNodesRequest(TEvNodeBroker::TEvListNodes::TPtr &ev) void TNodeBroker::ProcessDelayedListNodesRequests() { + THashSet processed; while (!DelayedListNodesRequests.empty()) { auto it = DelayedListNodesRequests.begin(); if (it->first > Epoch.Id) break; - ProcessListNodesRequest(it->second); + // Avoid processing more than one request from the same sender + if (processed.insert(it->second->Sender).second) { + ProcessListNodesRequest(it->second); + } DelayedListNodesRequests.erase(it); } } @@ -432,6 +467,11 @@ void TNodeBroker::PrepareEpochCache() Y_PROTOBUF_SUPPRESS_NODISCARD info.SerializeToString(&EpochCache); TabletCounters->Simple()[COUNTER_EPOCH_SIZE_BYTES].Set(EpochCache.Size()); + + EpochDeltasCache.clear(); + EpochDeltasVersions.clear(); + EpochDeltasEndOffsets.clear(); + TabletCounters->Simple()[COUNTER_EPOCH_DELTAS_SIZE_BYTES].Set(EpochDeltasCache.size()); } void TNodeBroker::AddNodeToEpochCache(const TNodeInfo &node) @@ -447,6 +487,17 @@ void TNodeBroker::AddNodeToEpochCache(const TNodeInfo &node) EpochCache += delta; TabletCounters->Simple()[COUNTER_EPOCH_SIZE_BYTES].Set(EpochCache.Size()); + + if (!EpochDeltasVersions.empty() && EpochDeltasVersions.back() + 1 != Epoch.Version) { + EpochDeltasCache.clear(); + EpochDeltasVersions.clear(); + EpochDeltasEndOffsets.clear(); + } + + EpochDeltasCache += delta; + EpochDeltasVersions.push_back(Epoch.Version); + EpochDeltasEndOffsets.push_back(EpochDeltasCache.size()); + TabletCounters->Simple()[COUNTER_EPOCH_DELTAS_SIZE_BYTES].Set(EpochDeltasCache.size()); } void TNodeBroker::SubscribeForConfigUpdates(const TActorContext &ctx) diff --git a/ydb/core/mind/node_broker_impl.h b/ydb/core/mind/node_broker_impl.h index d111ce59cec1..6e522e8b8792 100644 --- a/ydb/core/mind/node_broker_impl.h +++ b/ydb/core/mind/node_broker_impl.h @@ -341,6 +341,10 @@ class TNodeBroker : public TActor TSchedulerCookieHolder EpochTimerCookieHolder; TString EpochCache; + TString EpochDeltasCache; + TVector EpochDeltasVersions; + TVector EpochDeltasEndOffsets; + TTabletCountersBase* TabletCounters; TAutoPtr TabletCountersPtr; diff --git a/ydb/core/mind/node_broker_ut.cpp b/ydb/core/mind/node_broker_ut.cpp index 33156cd3af8a..55404659b077 100644 --- a/ydb/core/mind/node_broker_ut.cpp +++ b/ydb/core/mind/node_broker_ut.cpp @@ -858,6 +858,48 @@ Y_UNIT_TEST_SUITE(TNodeBrokerTest) { UNIT_ASSERT_VALUES_EQUAL(epoch1.GetId(), epoch.GetId() + 5); } + Y_UNIT_TEST(TestListNodesEpochDeltas) + { + TTestBasicRuntime runtime(8, false); + Setup(runtime, 10); + TActorId sender = runtime.AllocateEdgeActor(); + + WaitForEpochUpdate(runtime, sender); + WaitForEpochUpdate(runtime, sender); + + auto epoch0 = GetEpoch(runtime, sender); + CheckRegistration(runtime, sender, "host1", 1001, "host1.yandex.net", "1.2.3.4", + 1, 2, 3, 4, TStatus::OK, NODE1, epoch0.GetNextEnd()); + auto epoch1 = CheckFilteredNodesList(runtime, sender, {NODE1}, {}, 0, epoch0.GetVersion()); + CheckRegistration(runtime, sender, "host2", 1001, "host2.yandex.net", "1.2.3.5", + 1, 2, 3, 5, TStatus::OK, NODE2, epoch1.GetNextEnd()); + auto epoch2 = CheckFilteredNodesList(runtime, sender, {NODE2}, {}, 0, epoch1.GetVersion()); + CheckRegistration(runtime, sender, "host3", 1001, "host3.yandex.net", "1.2.3.6", + 1, 2, 3, 6, TStatus::OK, NODE3, epoch2.GetNextEnd()); + auto epoch3 = CheckFilteredNodesList(runtime, sender, {NODE3}, {}, 0, epoch2.GetVersion()); + + CheckFilteredNodesList(runtime, sender, {NODE1, NODE2, NODE3}, {}, 0, epoch0.GetVersion()); + CheckFilteredNodesList(runtime, sender, {NODE2, NODE3}, {}, 0, epoch1.GetVersion()); + CheckFilteredNodesList(runtime, sender, {}, {}, 0, epoch3.GetVersion()); + + RebootTablet(runtime, MakeNodeBrokerID(), sender); + CheckFilteredNodesList(runtime, sender, {}, {}, 0, epoch3.GetVersion()); + + CheckRegistration(runtime, sender, "host4", 1001, "host4.yandex.net", "1.2.3.7", + 1, 2, 3, 7, TStatus::OK, NODE4, epoch3.GetNextEnd()); + auto epoch4 = CheckFilteredNodesList(runtime, sender, {NODE4}, {}, 0, epoch3.GetVersion()); + + // NodeBroker doesn't have enough history in memory and replies with the full node list + CheckFilteredNodesList(runtime, sender, {NODE1, NODE2, NODE3, NODE4}, {}, 0, epoch2.GetVersion()); + + WaitForEpochUpdate(runtime, sender); + auto epoch5 = GetEpoch(runtime, sender); + CheckFilteredNodesList(runtime, sender, {}, {}, 0, epoch5.GetVersion()); + + // New epoch may remove nodes, so deltas are not returned on epoch change + CheckFilteredNodesList(runtime, sender, {NODE1, NODE2, NODE3, NODE4}, {}, 0, epoch3.GetVersion()); + } + Y_UNIT_TEST(TestRandomActions) { TTestBasicRuntime runtime(8, false); diff --git a/ydb/core/mon/async_http_mon.cpp b/ydb/core/mon/async_http_mon.cpp index 5542359baefd..8df05b03fbc9 100644 --- a/ydb/core/mon/async_http_mon.cpp +++ b/ydb/core/mon/async_http_mon.cpp @@ -246,8 +246,9 @@ class THttpMonLegacyActorRequest : public TActorBootstrappedCreateResponseString(response)); @@ -554,10 +555,26 @@ class THttpMonServiceNodeRequest : public TActorBootstrapped parser(response); + + NHttp::THeadersBuilder headers(parser.Headers); + headers.Set("X-Forwarded-From-Node", TStringBuilder() << Event->Sender.NodeId()); + + NHttp::THttpRenderer renderer; + renderer.InitRequest(parser.Method, parser.URL, parser.Protocol, parser.Version); + renderer.Set(headers); + if (parser.HaveBody()) { + renderer.SetBody(parser.Body); // it shouldn't be here, 30x with a body is a bad idea + } + renderer.Finish(); + return renderer.AsString(); + } + void Bootstrap() { NHttp::THttpConfig::SocketAddressType address; FromProto(address, Event->Get()->Record.GetAddress()); - NHttp::THttpIncomingRequestPtr request = new NHttp::THttpIncomingRequest(Event->Get()->Record.GetHttpRequest(), Endpoint, address); + NHttp::THttpIncomingRequestPtr request = new NHttp::THttpIncomingRequest(RewriteWithForwardedFromNode(Event->Get()->Record.GetHttpRequest()), Endpoint, address); TStringBuilder prefix; prefix << "/node/" << TActivationContext::ActorSystem()->NodeId; if (request->URL.SkipPrefix(prefix)) { diff --git a/ydb/core/node_whiteboard/node_whiteboard.h b/ydb/core/node_whiteboard/node_whiteboard.h index ea1ae0735e39..a1cdd1a12d4f 100644 --- a/ydb/core/node_whiteboard/node_whiteboard.h +++ b/ydb/core/node_whiteboard/node_whiteboard.h @@ -373,12 +373,6 @@ struct TEvWhiteboard{ TEvSystemStateUpdate(const TNodeLocation& systemLocation) { systemLocation.Serialize(Record.MutableLocation(), false); - const auto& x = systemLocation.GetLegacyValue(); - auto *pb = Record.MutableSystemLocation(); - pb->SetDataCenter(x.DataCenter); - pb->SetRoom(x.Room); - pb->SetRack(x.Rack); - pb->SetBody(x.Body); } TEvSystemStateUpdate(const NKikimrWhiteboard::TSystemStateInfo& systemStateInfo) { @@ -507,5 +501,38 @@ inline TActorId MakeNodeWhiteboardServiceId(ui32 node) { IActor* CreateNodeWhiteboardService(); -} // NTabletState +template +struct WhiteboardResponse {}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvTabletStateResponse; +}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvPDiskStateResponse; +}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvVDiskStateResponse; +}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvSystemStateResponse; +}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvBSGroupStateResponse; +}; + +template<> +struct WhiteboardResponse { + using Type = TEvWhiteboard::TEvNodeStateResponse; +}; + +} // NNodeWhiteboard } // NKikimr diff --git a/ydb/core/persqueue/blob.cpp b/ydb/core/persqueue/blob.cpp index 80027c17577b..5564d7cd03e2 100644 --- a/ydb/core/persqueue/blob.cpp +++ b/ydb/core/persqueue/blob.cpp @@ -422,7 +422,7 @@ void TBatch::Unpack() { PackedData.Clear(); } -void TBatch::UnpackTo(TVector *blobs) +void TBatch::UnpackTo(TVector *blobs) const { Y_ABORT_UNLESS(PackedData.size()); auto type = Header.GetFormat(); @@ -446,7 +446,7 @@ NScheme::TDataRef GetChunk(const char*& data, const char *end) return NScheme::TDataRef(data - size, size); } -void TBatch::UnpackToType1(TVector *blobs) { +void TBatch::UnpackToType1(TVector *blobs) const { Y_ABORT_UNLESS(Header.GetFormat() == NKikimrPQ::TBatchHeader::ECompressed); Y_ABORT_UNLESS(PackedData.size()); ui32 totalBlobs = Header.GetCount() + Header.GetInternalPartsCount(); @@ -606,7 +606,7 @@ void TBatch::UnpackToType1(TVector *blobs) { } } -void TBatch::UnpackToType0(TVector *blobs) { +void TBatch::UnpackToType0(TVector *blobs) const { Y_ABORT_UNLESS(Header.GetFormat() == NKikimrPQ::TBatchHeader::EUncompressed); Y_ABORT_UNLESS(PackedData.size()); ui32 shift = 0; @@ -640,7 +640,7 @@ ui32 TBatch::FindPos(const ui64 offset, const ui16 partNo) const { void THead::Clear() { Offset = PartNo = PackedSize = 0; - Batches.clear(); + ClearBatches(); } ui64 THead::GetNextOffset() const @@ -650,11 +650,7 @@ ui64 THead::GetNextOffset() const ui16 THead::GetInternalPartsCount() const { - ui16 res = 0; - for (auto& b : Batches) { - res += b.GetInternalPartsCount(); - } - return res; + return InternalPartsCount; } ui32 THead::GetCount() const @@ -675,15 +671,73 @@ IOutputStream& operator <<(IOutputStream& out, const THead& value) } ui32 THead::FindPos(const ui64 offset, const ui16 partNo) const { - ui32 i = 0; - for (; i < Batches.size(); ++i) { - //this batch contains blobs with position bigger than requested - if (Batches[i].GetOffset() > offset || Batches[i].GetOffset() == offset && Batches[i].GetPartNo() > partNo) - break; - } - if (i == 0) + if (Batches.empty()) { return Max(); - return i - 1; + } + + ui32 i = Batches.size() - 1; + while (i > 0 && Batches[i].IsGreaterThan(offset, partNo)) { + --i; + } + + if (i == 0) { + if (Batches[i].IsGreaterThan(offset, partNo)) { + return Max(); + } else { + return 0; + } + } + + return i; +} + +void THead::AddBatch(const TBatch& batch) { + auto& b = Batches.emplace_back(batch); + InternalPartsCount += b.GetInternalPartsCount(); +} + +void THead::ClearBatches() { + Batches.clear(); + InternalPartsCount = 0; +} + +const std::deque& THead::GetBatches() const { + return Batches; +} + +const TBatch& THead::GetBatch(ui32 idx) const { + return Batches.at(idx); +} + +const TBatch& THead::GetLastBatch() const { + Y_ABORT_UNLESS(!Batches.empty()); + return Batches.back(); +} + +TBatch THead::ExtractFirstBatch() { + Y_ABORT_UNLESS(!Batches.empty()); + auto batch = std::move(Batches.front()); + InternalPartsCount -= batch.GetInternalPartsCount(); + Batches.pop_front(); + return batch; +} + +THead::TBatchAccessor THead::MutableBatch(ui32 idx) { + Y_ABORT_UNLESS(idx < Batches.size()); + return TBatchAccessor(Batches[idx]); +} + +THead::TBatchAccessor THead::MutableLastBatch() { + Y_ABORT_UNLESS(!Batches.empty()); + return TBatchAccessor(Batches.back()); +} + +void THead::AddBlob(const TClientBlob& blob) { + Y_ABORT_UNLESS(!Batches.empty()); + auto& batch = Batches.back(); + InternalPartsCount -= batch.GetInternalPartsCount(); + batch.AddBlob(blob); + InternalPartsCount += batch.GetInternalPartsCount(); } TPartitionedBlob::TRenameFormedBlobInfo::TRenameFormedBlobInfo(const TKey& oldKey, const TKey& newKey, ui32 size) : @@ -832,7 +886,7 @@ auto TPartitionedBlob::CreateFormedBlob(ui32 size, bool useRename) -> std::optio GlueHead = GlueNewHead = false; if (!Blobs.empty()) { - TBatch batch{Offset, Blobs.front().GetPartNo(), std::move(Blobs)}; + auto batch = TBatch::FromBlobs(Offset, std::move(Blobs)); Blobs.clear(); batch.Pack(); Y_ABORT_UNLESS(batch.Packed); diff --git a/ydb/core/persqueue/blob.h b/ydb/core/persqueue/blob.h index 24aa479a2eaa..6ad52f28e42c 100644 --- a/ydb/core/persqueue/blob.h +++ b/ydb/core/persqueue/blob.h @@ -121,38 +121,30 @@ struct TBatch { TVector InternalPartsPos; NKikimrPQ::TBatchHeader Header; TBuffer PackedData; + TBatch() : Packed(false) { PackedData.Reserve(8_MB); } - TBatch(const ui64 offset, const ui16 partNo, const TVector& blobs) - : Packed(false) + TBatch(const ui64 offset, const ui16 partNo) + : TBatch() { - PackedData.Reserve(8_MB); Header.SetOffset(offset); Header.SetPartNo(partNo); Header.SetUnpackedSize(0); Header.SetCount(0); Header.SetInternalPartsCount(0); - for (auto& b : blobs) { - AddBlob(b); - } } - TBatch(const ui64 offset, const ui16 partNo, const std::deque& blobs) - : Packed(false) - { - PackedData.Reserve(8_MB); - Header.SetOffset(offset); - Header.SetPartNo(partNo); - Header.SetUnpackedSize(0); - Header.SetCount(0); - Header.SetInternalPartsCount(0); + static TBatch FromBlobs(const ui64 offset, std::deque&& blobs) { + Y_ABORT_UNLESS(!blobs.empty()); + TBatch batch(offset, blobs.front().GetPartNo()); for (auto& b : blobs) { - AddBlob(b); + batch.AddBlob(b); } + return batch; } void AddBlob(const TClientBlob &b) { @@ -187,6 +179,9 @@ struct TBatch { ui16 GetInternalPartsCount() const { return Header.GetInternalPartsCount(); } + bool IsGreaterThan(ui64 offset, ui16 partNo) const { + return GetOffset() > offset || GetOffset() == offset && GetPartNo() > partNo; + } TBatch(const NKikimrPQ::TBatchHeader &header, const char* data) : Packed(true) @@ -198,9 +193,9 @@ struct TBatch { ui32 GetPackedSize() const { Y_ABORT_UNLESS(Packed); return sizeof(ui16) + PackedData.size() + Header.ByteSize(); } void Pack(); void Unpack(); - void UnpackTo(TVector *result); - void UnpackToType0(TVector *result); - void UnpackToType1(TVector *result); + void UnpackTo(TVector *result) const; + void UnpackToType0(TVector *result) const; + void UnpackToType1(TVector *result) const; void SerializeTo(TString& res) const; @@ -232,14 +227,39 @@ class TBlobIterator { ui16 InternalPartsCount; }; +class TPartitionedBlob; + //THead represents bathes, stored in head(at most 8 Mb) struct THead { - std::deque Batches; //all batches except last must be packed // BlobsSize <= 512Kb // size of Blobs after packing must be <= BlobsSize //otherwise head will be compacted not in total, some blobs will still remain in head //PackedSize + BlobsSize must be <= 8Mb +private: + std::deque Batches; + ui16 InternalPartsCount = 0; + + friend class TPartitionedBlob; + + class TBatchAccessor { + TBatch& Batch; + + public: + explicit TBatchAccessor(TBatch& batch) + : Batch(batch) + {} + + void Pack() { + Batch.Pack(); + } + + void Unpack() { + Batch.Unpack(); + } + }; + +public: ui64 Offset; ui16 PartNo; ui32 PackedSize; @@ -261,6 +281,18 @@ struct THead { //return Max if not such pos in head //returns batch with such position ui32 FindPos(const ui64 offset, const ui16 partNo) const; + + void AddBatch(const TBatch& batch); + void ClearBatches(); + const std::deque& GetBatches() const; + const TBatch& GetBatch(ui32 idx) const; + const TBatch& GetLastBatch() const; + TBatchAccessor MutableBatch(ui32 idx); + TBatchAccessor MutableLastBatch(); + TBatch ExtractFirstBatch(); + void AddBlob(const TClientBlob& blob); + + friend IOutputStream& operator <<(IOutputStream& out, const THead& value); }; IOutputStream& operator <<(IOutputStream& out, const THead& value); diff --git a/ydb/core/persqueue/partition_init.cpp b/ydb/core/persqueue/partition_init.cpp index 5aecbada325b..e17775cbb93c 100644 --- a/ydb/core/persqueue/partition_init.cpp +++ b/ydb/core/persqueue/partition_init.cpp @@ -634,7 +634,7 @@ void TInitDataStep::Handle(TEvKeyValue::TEvResponse::TPtr &ev, const TActorConte Y_ABORT_UNLESS(size == read.GetValue().size()); for (TBlobIterator it(key, read.GetValue()); it.IsValid(); it.Next()) { - head.Batches.emplace_back(it.GetBatch()); + head.AddBatch(it.GetBatch()); } head.PackedSize += size; diff --git a/ydb/core/persqueue/partition_read.cpp b/ydb/core/persqueue/partition_read.cpp index 8ecac8565298..387b51ae1938 100644 --- a/ydb/core/persqueue/partition_read.cpp +++ b/ydb/core/persqueue/partition_read.cpp @@ -626,13 +626,13 @@ TVector TPartition::GetReadRequestFromHead( Y_ABORT_UNLESS(pos != Max()); } ui32 lastBlobSize = 0; - for (;pos < Head.Batches.size(); ++pos) { + for (;pos < Head.GetBatches().size(); ++pos) { TVector blobs; - Head.Batches[pos].UnpackTo(&blobs); + Head.GetBatch(pos).UnpackTo(&blobs); ui32 i = 0; - ui64 offset = Head.Batches[pos].GetOffset(); - ui16 pno = Head.Batches[pos].GetPartNo(); + ui64 offset = Head.GetBatch(pos).GetOffset(); + ui16 pno = Head.GetBatch(pos).GetPartNo(); for (; i < blobs.size(); ++i) { ui64 curOffset = offset; diff --git a/ydb/core/persqueue/partition_write.cpp b/ydb/core/persqueue/partition_write.cpp index c611c40bd3ac..a115a56d39cb 100644 --- a/ydb/core/persqueue/partition_write.cpp +++ b/ydb/core/persqueue/partition_write.cpp @@ -405,7 +405,7 @@ void TPartition::SyncMemoryStateWithKVState(const TActorContext& ctx) { Head.PackedSize = 0; Head.Offset = NewHead.Offset; Head.PartNo = NewHead.PartNo; //no partNo at this point - Head.Batches.clear(); + Head.ClearBatches(); } while (!CompactedKeys.empty()) { @@ -428,9 +428,8 @@ void TPartition::SyncMemoryStateWithKVState(const TActorContext& ctx) { } // head cleared, all data moved to body //append Head with newHead - while (!NewHead.Batches.empty()) { - Head.Batches.push_back(NewHead.Batches.front()); - NewHead.Batches.pop_front(); + while (!NewHead.GetBatches().empty()) { + Head.AddBatch(NewHead.ExtractFirstBatch()); } Head.PackedSize += NewHead.PackedSize; @@ -1324,22 +1323,22 @@ bool TPartition::ExecRequest(TWriteMsg& p, ProcessParameters& parameters, TEvKey ctx); ui32 countOfLastParts = 0; for (auto& x : PartitionedBlob.GetClientBlobs()) { - if (NewHead.Batches.empty() || NewHead.Batches.back().Packed) { - NewHead.Batches.emplace_back(curOffset, x.GetPartNo(), TVector()); + if (NewHead.GetBatches().empty() || NewHead.GetLastBatch().Packed) { + NewHead.AddBatch(TBatch(curOffset, x.GetPartNo())); NewHead.PackedSize += GetMaxHeaderSize(); //upper bound for packed size } if (x.IsLastPart()) { ++countOfLastParts; } - Y_ABORT_UNLESS(!NewHead.Batches.back().Packed); - NewHead.Batches.back().AddBlob(x); + Y_ABORT_UNLESS(!NewHead.GetLastBatch().Packed); + NewHead.AddBlob(x); NewHead.PackedSize += x.GetBlobSize(); - if (NewHead.Batches.back().GetUnpackedSize() >= BATCH_UNPACK_SIZE_BORDER) { - NewHead.Batches.back().Pack(); - NewHead.PackedSize += NewHead.Batches.back().GetPackedSize(); //add real packed size for this blob + if (NewHead.GetLastBatch().GetUnpackedSize() >= BATCH_UNPACK_SIZE_BORDER) { + NewHead.MutableLastBatch().Pack(); + NewHead.PackedSize += NewHead.GetLastBatch().GetPackedSize(); //add real packed size for this blob NewHead.PackedSize -= GetMaxHeaderSize(); //instead of upper bound - NewHead.PackedSize -= NewHead.Batches.back().GetUnpackedSize(); + NewHead.PackedSize -= NewHead.GetLastBatch().GetUnpackedSize(); } } @@ -1416,15 +1415,15 @@ void TPartition::AddNewWriteBlob(std::pair& res, TEvKeyValue::TEvReq valueD.reserve(res.second); ui32 pp = Head.FindPos(key.GetOffset(), key.GetPartNo()); if (pp < Max() && key.GetOffset() < EndOffset) { //this batch trully contains this offset - Y_ABORT_UNLESS(pp < Head.Batches.size()); - Y_ABORT_UNLESS(Head.Batches[pp].GetOffset() == key.GetOffset()); - Y_ABORT_UNLESS(Head.Batches[pp].GetPartNo() == key.GetPartNo()); - for (; pp < Head.Batches.size(); ++pp) { //TODO - merge small batches here - Y_ABORT_UNLESS(Head.Batches[pp].Packed); - Head.Batches[pp].SerializeTo(valueD); + Y_ABORT_UNLESS(pp < Head.GetBatches().size()); + Y_ABORT_UNLESS(Head.GetBatch(pp).GetOffset() == key.GetOffset()); + Y_ABORT_UNLESS(Head.GetBatch(pp).GetPartNo() == key.GetPartNo()); + for (; pp < Head.GetBatches().size(); ++pp) { //TODO - merge small batches here + Y_ABORT_UNLESS(Head.GetBatch(pp).Packed); + Head.GetBatch(pp).SerializeTo(valueD); } } - for (auto& b : NewHead.Batches) { + for (auto& b : NewHead.GetBatches()) { Y_ABORT_UNLESS(b.Packed); b.SerializeTo(valueD); } @@ -1701,7 +1700,7 @@ void TPartition::BeginAppendHeadWithNewWrites(const TActorContext& ctx) NewHead.PartNo = 0; NewHead.PackedSize = 0; - Y_ABORT_UNLESS(NewHead.Batches.empty()); + Y_ABORT_UNLESS(NewHead.GetBatches().empty()); Parameters->OldPartsCleared = false; Parameters->HeadCleared = (Head.PackedSize == 0); @@ -1746,12 +1745,12 @@ void TPartition::EndAppendHeadWithNewWrites(TEvKeyValue::TEvRequest* request, co UpdateWriteBufferIsFullState(ctx.Now()); - if (!NewHead.Batches.empty() && !NewHead.Batches.back().Packed) { - NewHead.Batches.back().Pack(); - NewHead.PackedSize += NewHead.Batches.back().GetPackedSize(); //add real packed size for this blob + if (!NewHead.GetBatches().empty() && !NewHead.GetLastBatch().Packed) { + NewHead.MutableLastBatch().Pack(); + NewHead.PackedSize += NewHead.GetLastBatch().GetPackedSize(); //add real packed size for this blob NewHead.PackedSize -= GetMaxHeaderSize(); //instead of upper bound - NewHead.PackedSize -= NewHead.Batches.back().GetUnpackedSize(); + NewHead.PackedSize -= NewHead.GetLastBatch().GetUnpackedSize(); } Y_ABORT_UNLESS((Parameters->HeadCleared ? 0 : Head.PackedSize) + NewHead.PackedSize <= MaxBlobSize); //otherwise last PartitionedBlob.Add must compact all except last cl diff --git a/ydb/core/persqueue/ut/internals_ut.cpp b/ydb/core/persqueue/ut/internals_ut.cpp index 14c3414e2bbb..a151447b4f3d 100644 --- a/ydb/core/persqueue/ut/internals_ut.cpp +++ b/ydb/core/persqueue/ut/internals_ut.cpp @@ -39,38 +39,38 @@ void Test(bool headCompacted, ui32 parts, ui32 partSize, ui32 leftInHead) THead head; head.Offset = 100; TString value(100_KB, 'a'); - head.Batches.push_back(TBatch(head.Offset, 0, TVector())); + head.AddBatch(TBatch(head.Offset, 0)); for (ui32 i = 0; i < 50; ++i) { - head.Batches.back().AddBlob(TClientBlob( + head.AddBlob(TClientBlob( "sourceId" + TString(1,'a' + rand() % 26), i + 1, value, TMaybe(), TInstant::MilliSeconds(i + 1), TInstant::MilliSeconds(i + 1), 1, "", "" )); if (!headCompacted) - all.push_back(head.Batches.back().Blobs.back()); + all.push_back(head.GetLastBatch().Blobs.back()); } - head.Batches.back().Pack(); - UNIT_ASSERT(head.Batches.back().Header.GetFormat() == NKikimrPQ::TBatchHeader::ECompressed); - head.Batches.back().Unpack(); - head.Batches.back().Pack(); + head.MutableLastBatch().Pack(); + UNIT_ASSERT(head.GetLastBatch().Header.GetFormat() == NKikimrPQ::TBatchHeader::ECompressed); + head.MutableLastBatch().Unpack(); + head.MutableLastBatch().Pack(); TString str; - head.Batches.back().SerializeTo(str); + head.GetLastBatch().SerializeTo(str); auto header = ExtractHeader(str.c_str(), str.size()); TBatch batch(header, str.c_str() + header.ByteSize() + sizeof(ui16)); batch.Unpack(); - head.PackedSize = head.Batches.back().GetPackedSize(); - UNIT_ASSERT(head.Batches.back().GetUnpackedSize() + GetMaxHeaderSize() >= head.Batches.back().GetPackedSize()); + head.PackedSize = head.GetLastBatch().GetPackedSize(); + UNIT_ASSERT(head.GetLastBatch().GetUnpackedSize() + GetMaxHeaderSize() >= head.GetLastBatch().GetPackedSize()); THead newHead; newHead.Offset = head.GetNextOffset(); - newHead.Batches.push_back(TBatch(newHead.Offset, 0, TVector())); + newHead.AddBatch(TBatch(newHead.Offset, 0)); for (ui32 i = 0; i < 10; ++i) { - newHead.Batches.back().AddBlob(TClientBlob( + newHead.AddBlob(TClientBlob( "sourceId2", i + 1, value, TMaybe(), TInstant::MilliSeconds(i + 1000), TInstant::MilliSeconds(i + 1000), 1, "", "" )); - all.push_back(newHead.Batches.back().Blobs.back()); //newHead always glued + all.push_back(newHead.GetLastBatch().Blobs.back()); //newHead always glued } - newHead.PackedSize = newHead.Batches.back().GetUnpackedSize(); + newHead.PackedSize = newHead.GetLastBatch().GetUnpackedSize(); TString value2(partSize, 'b'); ui32 maxBlobSize = 8 << 20; TPartitionedBlob blob(TPartitionId(0), newHead.GetNextOffset(), "sourceId3", 1, parts, parts * value2.size(), head, newHead, headCompacted, false, maxBlobSize); @@ -125,16 +125,16 @@ void Test(bool headCompacted, ui32 parts, ui32 partSize, ui32 leftInHead) if (formed.empty()) { //nothing compacted - newHead must be here if (!headCompacted) { - for (auto& p : head.Batches) { - p.Unpack(); - for (const auto& b : p.Blobs) + for (ui32 pp = 0; pp < head.GetBatches().size(); ++pp) { + head.MutableBatch(pp).Unpack(); + for (const auto& b : head.GetBatch(pp).Blobs) real.push_back(b); } } - for (auto& p : newHead.Batches) { - p.Unpack(); - for (const auto& b : p.Blobs) + for (ui32 pp = 0; pp < newHead.GetBatches().size(); ++pp) { + newHead.MutableBatch(pp).Unpack(); + for (const auto& b : newHead.GetBatch(pp).Blobs) real.push_back(b); } } diff --git a/ydb/core/protos/cms.proto b/ydb/core/protos/cms.proto index 95994700860e..f91f2c0742de 100644 --- a/ydb/core/protos/cms.proto +++ b/ydb/core/protos/cms.proto @@ -110,13 +110,29 @@ message TAction { REBOOT_HOST = 10; // Same as SHUTDOWN_HOST, but forcibly allowed if host is down } - optional EType Type = 1; - optional string Host = 2; - repeated string Services = 3; - repeated string Devices = 4; - optional uint64 Duration = 5; + message TIssue { + enum EType { + UNKNOWN = 0; + GENERIC = 1; + TOO_MANY_UNAVAILABLE_VDISKS = 2; + TOO_MANY_UNAVAILABLE_STATE_STORAGE_RINGS = 3; + DISABLED_NODES_LIMIT_REACHED = 4; + TENANT_DISABLED_NODES_LIMIT_REACHED = 5; + SYS_TABLETS_NODE_LIMIT_REACHED = 6; + } + + optional EType Type = 1; + optional string Message = 2; + } + + optional EType Type = 1; + optional string Host = 2; + repeated string Services = 3; + repeated string Devices = 4; + optional uint64 Duration = 5; // If specified will be expanded to list of hosts. - optional string Tenant = 6; + optional string Tenant = 6; + optional TIssue Issue = 7; } enum ETenantPolicy { diff --git a/ydb/core/protos/config.proto b/ydb/core/protos/config.proto index 1bf74c929823..5757e561370e 100644 --- a/ydb/core/protos/config.proto +++ b/ydb/core/protos/config.proto @@ -1853,6 +1853,10 @@ message TMetadataCacheConfig { optional uint64 RefreshPeriodMs = 1 [default = 15000]; } +message TShutdownConfig { + optional uint32 MinDelayBeforeShutdownSeconds = 1; +} + message TLabel { optional string Name = 1; optional string Value = 2; @@ -1937,6 +1941,7 @@ message TAppConfig { //optional TMemoryControllerConfig MemoryControllerConfig = 81; NB. exist in main optional TGroupedMemoryLimiterConfig GroupedMemoryLimiterConfig = 82; optional NKikimrReplication.TReplicationDefaults ReplicationConfig = 83; + optional TShutdownConfig ShutdownConfig = 84; repeated TNamedConfig NamedConfigs = 100; optional string ClusterYamlConfig = 101; diff --git a/ydb/core/protos/counters_node_broker.proto b/ydb/core/protos/counters_node_broker.proto index 936b268ae692..86a24a1ac5f3 100644 --- a/ydb/core/protos/counters_node_broker.proto +++ b/ydb/core/protos/counters_node_broker.proto @@ -8,6 +8,7 @@ option (NKikimr.TabletTypeName) = "NodeBroker"; // Used as prefix for all counte enum ESimpleCounters { COUNTER_EPOCH_SIZE_BYTES = 0 [(CounterOpts) = {Name: "EpochSizeBytes"}]; + COUNTER_EPOCH_DELTAS_SIZE_BYTES = 1 [(CounterOpts) = {Name: "EpochDeltasSizeBytes"}]; } enum ECumulativeCounters { diff --git a/ydb/core/protos/feature_flags.proto b/ydb/core/protos/feature_flags.proto index af68aa5f57d0..7a6ce9640f3c 100644 --- a/ydb/core/protos/feature_flags.proto +++ b/ydb/core/protos/feature_flags.proto @@ -116,7 +116,7 @@ message TFeatureFlags { optional bool EnableIcNodeCache = 101 [default = true]; optional bool EnableTempTables = 102 [default = false]; optional bool SuppressCompatibilityCheck = 103 [default = false]; - optional bool EnableUniqConstraint = 104 [default = false]; + optional bool EnableUniqConstraint = 104 [default = true]; optional bool EnableChangefeedDebeziumJsonFormat = 105 [default = false]; optional bool EnableStatistics = 106 [default = true]; optional bool EnableUuidAsPrimaryKey = 107 [default = true]; diff --git a/ydb/core/protos/flat_scheme_op.proto b/ydb/core/protos/flat_scheme_op.proto index 47005e41c8e6..a5652056a667 100644 --- a/ydb/core/protos/flat_scheme_op.proto +++ b/ydb/core/protos/flat_scheme_op.proto @@ -11,6 +11,7 @@ import "ydb/core/protos/filestore_config.proto"; import "ydb/core/protos/channel_purpose.proto"; import "ydb/core/protos/follower_group.proto"; import "ydb/core/protos/blob_depot_config.proto"; +import "ydb/core/protos/yql_translation_settings.proto"; import "ydb/public/api/protos/ydb_coordination.proto"; import "ydb/public/api/protos/ydb_export.proto"; import "ydb/public/api/protos/ydb_value.proto"; @@ -1815,6 +1816,7 @@ message TDescribeOptions { optional bool ReturnChannelsBinding = 8 [default = false]; optional bool ReturnRangeKey = 9 [default = true]; optional bool ReturnSetVal = 10 [default = false]; + optional bool ReturnIndexTableBoundaries = 11 [default = false]; } // Request to read scheme for a specific path @@ -2127,6 +2129,7 @@ message TViewDescription { optional NKikimrProto.TPathID PathId = 2; optional uint64 Version = 3; optional string QueryText = 4; + optional NYql.NProto.TTranslationSettings CapturedContext = 5; } message TResourcePoolProperties { diff --git a/ydb/core/protos/node_whiteboard.proto b/ydb/core/protos/node_whiteboard.proto index 090fd2909c75..fe5e2a6fafae 100644 --- a/ydb/core/protos/node_whiteboard.proto +++ b/ydb/core/protos/node_whiteboard.proto @@ -11,6 +11,7 @@ option java_package = "ru.yandex.kikimr.proto"; extend google.protobuf.FieldOptions { optional uint64 InsignificantChangeAmount = 70553; optional uint32 InsignificantChangePercent = 70554; + optional bool DefaultField = 70555; } enum EFlag { @@ -47,28 +48,29 @@ message TTabletStateInfo { Reserved16 = 16; } - optional uint64 TabletId = 1; + optional uint64 TabletId = 1 [(DefaultField) = true]; optional uint64 CreateTime = 2; - optional uint64 ChangeTime = 3; - optional ETabletState State = 4; + optional uint64 ChangeTime = 3 [(DefaultField) = true]; + optional ETabletState State = 4 [(DefaultField) = true]; optional uint32 UserState = 5; // implementation-dependent - optional uint32 Generation = 6; - optional NKikimrTabletBase.TTabletTypes.EType Type = 7; + optional uint32 Generation = 6 [(DefaultField) = true]; + optional NKikimrTabletBase.TTabletTypes.EType Type = 7 [(DefaultField) = true]; optional string Host = 8; repeated uint32 ChannelGroupIDs = 9; // BS Group per channel repeated TCustomTabletAttribute Attributes = 10; optional uint32 NodeId = 11; // filled during merge - optional bool Leader = 12; // leader or follower + optional bool Leader = 12 [(DefaultField) = true]; // leader or follower optional uint32 Count = 13; // filled during group count - optional uint32 FollowerId = 14; + optional uint32 FollowerId = 14 [(DefaultField) = true]; optional EFlag Overall = 15; // filled during merge - optional NKikimrSubDomains.TDomainKey TenantId = 16; - optional fixed64 HiveId = 17; + optional NKikimrSubDomains.TDomainKey TenantId = 16 [(DefaultField) = true]; + optional fixed64 HiveId = 17 [(DefaultField) = true]; optional string EndOfRangeKeyPrefix = 18; // filled during merge } message TEvTabletStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; optional string Format = 5; // it could be "packed5" optional string GroupBy = 20; // it's either empty or "Type,State" for now repeated fixed64 FilterTabletId = 22; @@ -99,6 +101,7 @@ message TNodeStateInfo { message TEvNodeStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; } message TEvNodeStateResponse { @@ -108,33 +111,35 @@ message TEvNodeStateResponse { } message TPDiskStateInfo { - optional uint32 PDiskId = 1; + optional uint32 PDiskId = 1 [(DefaultField) = true]; optional uint64 CreateTime = 2; - optional uint64 ChangeTime = 3; - optional string Path = 4; - optional uint64 Guid = 5; - optional uint64 Category = 6; - optional uint64 AvailableSize = 7 [(InsignificantChangeAmount) = 104857600]; // 100Mb - optional uint64 TotalSize = 8; - optional NKikimrBlobStorage.TPDiskState.E State = 9; + optional uint64 ChangeTime = 3 [(DefaultField) = true]; + optional string Path = 4 [(DefaultField) = true]; + optional uint64 Guid = 5 [(DefaultField) = true]; + optional uint64 Category = 6 [(DefaultField) = true]; + optional uint64 AvailableSize = 7 [(DefaultField) = true, (InsignificantChangeAmount) = 104857600]; // 100Mb + optional uint64 TotalSize = 8 [(DefaultField) = true]; + optional NKikimrBlobStorage.TPDiskState.E State = 9 [(DefaultField) = true]; optional uint32 NodeId = 10; // filled during merge optional uint32 Count = 13; // filled during group count - optional EFlag Device = 14; - optional EFlag Realtime = 15; + optional EFlag Device = 14 [(DefaultField) = true]; + optional EFlag Realtime = 15 [(DefaultField) = true]; // State as flag - to be filled optional EFlag StateFlag = 16; // overall state - to be filled optional EFlag Overall = 17; - optional string SerialNumber = 18; - optional uint64 SystemSize = 19; - optional uint64 LogUsedSize = 20; - optional uint64 LogTotalSize = 21; - optional uint32 ExpectedSlotCount = 22; - optional uint64 EnforcedDynamicSlotSize = 23; + optional string SerialNumber = 18 [(DefaultField) = true]; + optional uint64 SystemSize = 19 [(DefaultField) = true]; + optional uint64 LogUsedSize = 20 [(DefaultField) = true]; + optional uint64 LogTotalSize = 21 [(DefaultField) = true]; + optional uint32 ExpectedSlotCount = 22 [(DefaultField) = true]; + optional uint64 EnforcedDynamicSlotSize = 23 [(DefaultField) = true]; + optional uint32 NumActiveSlots = 24 [(DefaultField) = true]; } message TEvPDiskStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; } message TEvPDiskStateResponse { @@ -171,13 +176,13 @@ message TVDiskSatisfactionRank { } message TVDiskStateInfo { - optional NKikimrBlobStorage.TVDiskID VDiskId = 1; + optional NKikimrBlobStorage.TVDiskID VDiskId = 1 [(DefaultField) = true]; optional uint64 CreateTime = 2; - optional uint64 ChangeTime = 3; - optional uint32 PDiskId = 4; - optional uint32 VDiskSlotId = 5; - optional uint64 Guid = 6; - optional uint64 Kind = 7; + optional uint64 ChangeTime = 3 [(DefaultField) = true]; + optional uint32 PDiskId = 4 [(DefaultField) = true]; + optional uint32 VDiskSlotId = 5 [(DefaultField) = true]; + optional uint64 Guid = 6 [(DefaultField) = true]; + optional uint64 Kind = 7 [(DefaultField) = true]; optional uint32 NodeId = 9; // filled during merge optional uint32 Count = 17; // filled during group count @@ -185,48 +190,49 @@ message TVDiskStateInfo { optional EFlag Overall = 10; // Current state of VDisk - optional EVDiskState VDiskState = 11; + optional EVDiskState VDiskState = 11 [(DefaultField) = true]; // Disk space flags - optional EFlag DiskSpace = 12; + optional EFlag DiskSpace = 12 [(DefaultField) = true]; // Compaction satisfaction rank - optional TVDiskSatisfactionRank SatisfactionRank = 13; + optional TVDiskSatisfactionRank SatisfactionRank = 13 [(DefaultField) = true]; // Is VDisk replicated? (i.e. contains all blobs it must have) - optional bool Replicated = 14; + optional bool Replicated = 14 [(DefaultField) = true]; // Does this VDisk has any yet unreplicated phantom-like blobs? - optional bool UnreplicatedPhantoms = 20 [default = false]; + optional bool UnreplicatedPhantoms = 20 [default = false, (DefaultField) = true]; // The same for the non-phantom-like blobs. - optional bool UnreplicatedNonPhantoms = 21 [default = false]; + optional bool UnreplicatedNonPhantoms = 21 [default = false, (DefaultField) = true]; // Replication progress (0 to 1). Only for replication, not blob scrubbing. - optional float ReplicationProgress = 30; + optional float ReplicationProgress = 30 [(DefaultField) = true]; // Replication ETA. - optional uint32 ReplicationSecondsRemaining = 31; + optional uint32 ReplicationSecondsRemaining = 31 [(DefaultField) = true]; // How many unsynced VDisks from current BlobStorage group we see - optional uint64 UnsyncedVDisks = 15 [default = 0]; + optional uint64 UnsyncedVDisks = 15 [default = 0, (DefaultField) = true]; // How much this VDisk have allocated on corresponding PDisk - optional uint64 AllocatedSize = 16 [(InsignificantChangeAmount) = 536870912]; // 512MiB + optional uint64 AllocatedSize = 16 [(InsignificantChangeAmount) = 536870912, (DefaultField) = true]; // 512MiB // How much space is available for VDisk corresponding to PDisk's hard space limits - optional uint64 AvailableSize = 28 [(InsignificantChangeAmount) = 536870912]; // 512MiB + optional uint64 AvailableSize = 28 [(InsignificantChangeAmount) = 536870912, (DefaultField) = true]; // 512MiB // Does this disk has some unreadable but not yet restored blobs? - optional bool HasUnreadableBlobs = 24; - optional fixed64 IncarnationGuid = 25; - optional bool DonorMode = 26; - optional fixed64 InstanceGuid = 27; // VDisk actor instance guid - repeated NKikimrBlobStorage.TVSlotId Donors = 29; + optional bool HasUnreadableBlobs = 24 [(DefaultField) = true]; + optional fixed64 IncarnationGuid = 25 [(DefaultField) = true]; + optional bool DonorMode = 26 [(DefaultField) = true]; + optional fixed64 InstanceGuid = 27 [(DefaultField) = true]; // VDisk actor instance guid + repeated NKikimrBlobStorage.TVSlotId Donors = 29 [(DefaultField) = true]; // VDisk (Skeleton) Front Queue Status - optional EFlag FrontQueues = 18; + optional EFlag FrontQueues = 18 [(DefaultField) = true]; // VDisk storage pool label - optional string StoragePoolName = 19; + optional string StoragePoolName = 19 [(DefaultField) = true]; // Read bytes per second from PDisk for TEvVGet blobs only - optional uint64 ReadThroughput = 22; + optional uint64 ReadThroughput = 22 [(DefaultField) = true]; // Write bytes per second to PDisk for TEvVPut blobs and replication bytes only - optional uint64 WriteThroughput = 23; + optional uint64 WriteThroughput = 23 [(DefaultField) = true]; } message TEvVDiskStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; } message TEvVDiskStateResponse { @@ -236,29 +242,30 @@ message TEvVDiskStateResponse { } message TBSGroupStateInfo { - optional uint32 GroupID = 1; - optional string ErasureSpecies = 2; - repeated NKikimrBlobStorage.TVDiskID VDiskIds = 3; + optional uint32 GroupID = 1 [(DefaultField) = true]; + optional string ErasureSpecies = 2 [(DefaultField) = true]; + repeated NKikimrBlobStorage.TVDiskID VDiskIds = 3 [(DefaultField) = true]; optional uint64 ChangeTime = 4; optional uint32 NodeId = 5; // filled during merge - optional uint32 GroupGeneration = 6; + optional uint32 GroupGeneration = 6 [(DefaultField) = true]; optional EFlag Overall = 7; - optional EFlag Latency = 8; + optional EFlag Latency = 8 [(DefaultField) = true]; optional uint32 Count = 13; // filled during group count - optional string StoragePoolName = 14; // from BS_CONTROLLER - optional uint64 AllocatedSize = 15 [(InsignificantChangeAmount) = 100000000]; - optional uint64 AvailableSize = 16 [(InsignificantChangeAmount) = 100000000]; - optional uint64 ReadThroughput = 17; - optional uint64 WriteThroughput = 18; - optional bool Encryption = 19; - repeated uint32 VDiskNodeIds = 20; - optional uint64 BlobDepotId = 21; // if set, then this is virtual group - optional bool NoVDisksInGroup = 22; - optional uint64 BlobDepotOnlineTime = 23; + optional string StoragePoolName = 14 [(DefaultField) = true]; // from BS_CONTROLLER + optional uint64 AllocatedSize = 15 [(InsignificantChangeAmount) = 100000000, (DefaultField) = true]; + optional uint64 AvailableSize = 16 [(InsignificantChangeAmount) = 100000000, (DefaultField) = true]; + optional uint64 ReadThroughput = 17 [(DefaultField) = true]; + optional uint64 WriteThroughput = 18 [(DefaultField) = true]; + optional bool Encryption = 19 [(DefaultField) = true]; + repeated uint32 VDiskNodeIds = 20 [(DefaultField) = true]; + optional uint64 BlobDepotId = 21 [(DefaultField) = true]; // if set, then this is virtual group + optional bool NoVDisksInGroup = 22 [(DefaultField) = true]; + optional uint64 BlobDepotOnlineTime = 23 [(DefaultField) = true]; } message TEvBSGroupStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; } message TEvBSGroupStateResponse { @@ -297,12 +304,12 @@ message TSystemStateInfo { optional uint64 LimitBytes = 2; } - optional uint64 StartTime = 1; - optional uint64 ChangeTime = 2; + optional uint64 StartTime = 1 [(DefaultField) = true]; + optional uint64 ChangeTime = 2 [(DefaultField) = true]; optional TLegacyNodeLocation SystemLocation = 3; - repeated double LoadAverage = 4; - optional uint32 NumberOfCpus = 5; - optional EFlag SystemState = 6; + repeated double LoadAverage = 4 [(DefaultField) = true]; + optional uint32 NumberOfCpus = 5 [(DefaultField) = true]; + optional EFlag SystemState = 6 [(DefaultField) = true]; optional EFlag MessageBusState = 7; optional EFlag GRpcState = 8; optional uint32 NodeId = 9; // filled during merge @@ -313,28 +320,29 @@ message TSystemStateInfo { optional uint32 RackId = 17; optional string Rack = 18; optional string Host = 19; - optional string Version = 20; - repeated TPoolStats PoolStats = 21; - repeated TEndpoint Endpoints = 22; - repeated string Roles = 23; - repeated string Tenants = 24; - optional string ClusterName = 25; - optional uint64 MemoryUsed = 26; - optional uint64 MemoryLimit = 27; + optional string Version = 20 [(DefaultField) = true]; + repeated TPoolStats PoolStats = 21 [(DefaultField) = true]; + repeated TEndpoint Endpoints = 22 [(DefaultField) = true]; + repeated string Roles = 23 [(DefaultField) = true]; + repeated string Tenants = 24 [(DefaultField) = true]; + optional string ClusterName = 25 [(DefaultField) = true]; + optional uint64 MemoryUsed = 26 [(DefaultField) = true]; + optional uint64 MemoryLimit = 27 [(DefaultField) = true]; optional EConfigState ConfigState = 28 [default = Consistent]; optional uint64 MemoryUsedInAlloc = 29; - optional double MaxDiskUsage = 30; - optional NActorsInterconnect.TNodeLocation Location = 31; + optional double MaxDiskUsage = 30 [(DefaultField) = true]; + optional NActorsInterconnect.TNodeLocation Location = 31 [(DefaultField) = true]; optional int64 MaxClockSkewWithPeerUs = 32; // a positive value means the peer is ahead in time; a negative value means it's behind optional uint32 MaxClockSkewPeerId = 33; optional uint64 DisconnectTime = 34; - optional TNodeSharedCache SharedCacheStats = 35; - optional uint32 TotalSessions = 36; - optional string NodeName = 37; + optional TNodeSharedCache SharedCacheStats = 35; // TODO: use memory stats + optional uint32 TotalSessions = 36 [(DefaultField) = true]; + optional string NodeName = 37 [(DefaultField) = true]; } message TEvSystemStateRequest { optional uint64 ChangedSince = 1; + repeated int32 FieldsRequired = 2 [packed = true]; } message TEvSystemStateResponse { diff --git a/ydb/core/protos/out/out_cms.cpp b/ydb/core/protos/out/out_cms.cpp index 71012587da8b..868e4d651608 100644 --- a/ydb/core/protos/out/out_cms.cpp +++ b/ydb/core/protos/out/out_cms.cpp @@ -29,3 +29,7 @@ Y_DECLARE_OUT_SPEC(, NKikimrCms::TLogRecordData::EType, stream, value) { Y_DECLARE_OUT_SPEC(, NKikimrCms::TAction::EType, stream, value) { stream << NKikimrCms::TAction::EType_Name(value); } + +Y_DECLARE_OUT_SPEC(, NKikimrCms::TAction::TIssue::EType, stream, value) { + stream << NKikimrCms::TAction::TIssue::EType_Name(value); +} diff --git a/ydb/core/protos/ya.make b/ydb/core/protos/ya.make index 535eb29dd3f3..96ea8d460c66 100644 --- a/ydb/core/protos/ya.make +++ b/ydb/core/protos/ya.make @@ -144,6 +144,7 @@ SRCS( tx_sequenceshard.proto ydb_result_set_old.proto ydb_table_impl.proto + yql_translation_settings.proto ) GENERATE_ENUM_SERIALIZATION(blobstorage_pdisk_config.pb.h) diff --git a/ydb/core/protos/yql_translation_settings.proto b/ydb/core/protos/yql_translation_settings.proto new file mode 100644 index 000000000000..515900b14d29 --- /dev/null +++ b/ydb/core/protos/yql_translation_settings.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package NYql.NProto; + +option java_package = "ru.yandex.kikimr.proto"; + +message TTranslationSettings { + optional string PathPrefix = 1; + optional uint32 SyntaxVersion = 2; + optional bool AnsiLexer = 3; + optional bool PgParser = 4; + + repeated string Pragmas = 10; +} diff --git a/ydb/core/tablet/node_whiteboard.cpp b/ydb/core/tablet/node_whiteboard.cpp index f9ffcfb1fd73..53468fec49d0 100644 --- a/ydb/core/tablet/node_whiteboard.cpp +++ b/ydb/core/tablet/node_whiteboard.cpp @@ -392,6 +392,136 @@ class TNodeWhiteboardService : public TActorBootstrapped return modified; } + static void CopyField(::google::protobuf::Message& protoTo, + const ::google::protobuf::Message& protoFrom, + const ::google::protobuf::Reflection& reflectionTo, + const ::google::protobuf::Reflection& reflectionFrom, + const ::google::protobuf::FieldDescriptor* field) { + using namespace ::google::protobuf; + if (field->is_repeated()) { + FieldDescriptor::CppType type = field->cpp_type(); + int size = reflectionFrom.FieldSize(protoFrom, field); + if (size != 0) { + reflectionTo.ClearField(&protoTo, field); + for (int i = 0; i < size; ++i) { + switch (type) { + case FieldDescriptor::CPPTYPE_INT32: + reflectionTo.AddInt32(&protoTo, field, reflectionFrom.GetRepeatedInt32(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_INT64: + reflectionTo.AddInt64(&protoTo, field, reflectionFrom.GetRepeatedInt64(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_UINT32: + reflectionTo.AddUInt32(&protoTo, field, reflectionFrom.GetRepeatedUInt32(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_UINT64: + reflectionTo.AddUInt64(&protoTo, field, reflectionFrom.GetRepeatedUInt64(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_DOUBLE: + reflectionTo.AddDouble(&protoTo, field, reflectionFrom.GetRepeatedDouble(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_FLOAT: + reflectionTo.AddFloat(&protoTo, field, reflectionFrom.GetRepeatedFloat(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_BOOL: + reflectionTo.AddBool(&protoTo, field, reflectionFrom.GetRepeatedBool(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_ENUM: + reflectionTo.AddEnum(&protoTo, field, reflectionFrom.GetRepeatedEnum(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_STRING: + reflectionTo.AddString(&protoTo, field, reflectionFrom.GetRepeatedString(protoFrom, field, i)); + break; + case FieldDescriptor::CPPTYPE_MESSAGE: + reflectionTo.AddMessage(&protoTo, field)->CopyFrom(reflectionFrom.GetRepeatedMessage(protoFrom, field, i)); + break; + } + } + } + } else { + if (reflectionFrom.HasField(protoFrom, field)) { + FieldDescriptor::CppType type = field->cpp_type(); + switch (type) { + case FieldDescriptor::CPPTYPE_INT32: + reflectionTo.SetInt32(&protoTo, field, reflectionFrom.GetInt32(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_INT64: + reflectionTo.SetInt64(&protoTo, field, reflectionFrom.GetInt64(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_UINT32: + reflectionTo.SetUInt32(&protoTo, field, reflectionFrom.GetUInt32(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_UINT64: + reflectionTo.SetUInt64(&protoTo, field, reflectionFrom.GetUInt64(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_DOUBLE: + reflectionTo.SetDouble(&protoTo, field, reflectionFrom.GetDouble(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_FLOAT: + reflectionTo.SetFloat(&protoTo, field, reflectionFrom.GetFloat(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_BOOL: + reflectionTo.SetBool(&protoTo, field, reflectionFrom.GetBool(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_ENUM: + reflectionTo.SetEnum(&protoTo, field, reflectionFrom.GetEnum(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_STRING: + reflectionTo.SetString(&protoTo, field, reflectionFrom.GetString(protoFrom, field)); + break; + case FieldDescriptor::CPPTYPE_MESSAGE: + reflectionTo.MutableMessage(&protoTo, field)->CopyFrom(reflectionFrom.GetMessage(protoFrom, field)); + break; + } + } + } + } + + static void SelectiveCopy(::google::protobuf::Message& protoTo, const ::google::protobuf::Message& protoFrom, const ::google::protobuf::RepeatedField& fields) { + using namespace ::google::protobuf; + const Descriptor& descriptor = *protoTo.GetDescriptor(); + const Reflection& reflectionTo = *protoTo.GetReflection(); + const Reflection& reflectionFrom = *protoFrom.GetReflection(); + for (auto fieldNumber : fields) { + const FieldDescriptor* field = descriptor.FindFieldByNumber(fieldNumber); + if (field) { + CopyField(protoTo, protoFrom, reflectionTo, reflectionFrom, field); + } + } + } + + template + static ::google::protobuf::RepeatedField GetDefaultFields(const TMessage& message) { + using namespace ::google::protobuf; + const Descriptor& descriptor = *message.GetDescriptor(); + ::google::protobuf::RepeatedField defaultFields; + int fieldCount = descriptor.field_count(); + for (int index = 0; index < fieldCount; ++index) { + const FieldDescriptor* field = descriptor.field(index); + const auto& options(field->options()); + if (options.HasExtension(NKikimrWhiteboard::DefaultField)) { + if (options.GetExtension(NKikimrWhiteboard::DefaultField)) { + defaultFields.Add(field->number()); + } + } + } + return defaultFields; + } + + template + static void Copy(TMessage& to, const TMessage& from, const TRequest& request) { + if (request.FieldsRequiredSize() > 0) { + if (request.FieldsRequiredSize() == 1 && request.GetFieldsRequired(0) == -1) { // all fields + to.CopyFrom(from); + } else { + SelectiveCopy(to, from, request.GetFieldsRequired()); + } + } else { + static auto defaultFields = GetDefaultFields(to); + SelectiveCopy(to, from, defaultFields); + } + } + void SetRole(TStringBuf roleName) { for (const auto& role : SystemStateInfo.GetRoles()) { if (role == roleName) { @@ -686,14 +816,6 @@ class TNodeWhiteboardService : public TActorBootstrapped } } - static void CopyTabletStateInfo( - NKikimrWhiteboard::TTabletStateInfo& dst, - const NKikimrWhiteboard::TTabletStateInfo& src, - const NKikimrWhiteboard::TEvTabletStateRequest&) - { - dst = src; - } - void Handle(TEvWhiteboard::TEvTabletStateRequest::TPtr &ev, const TActorContext &ctx) { auto now = TMonotonic::Now(); const auto& request = ev->Get()->Record; @@ -716,7 +838,7 @@ class TNodeWhiteboardService : public TActorBootstrapped for (const auto& pr : TabletStateInfo) { if (pr.second.changetime() >= changedSince) { NKikimrWhiteboard::TTabletStateInfo& tabletStateInfo = *record.add_tabletstateinfo(); - CopyTabletStateInfo(tabletStateInfo, pr.second, request); + Copy(tabletStateInfo, pr.second, request); } } } else { @@ -725,12 +847,12 @@ class TNodeWhiteboardService : public TActorBootstrapped if (it != TabletStateInfo.end()) { if (it->second.changetime() >= changedSince) { NKikimrWhiteboard::TTabletStateInfo& tabletStateInfo = *record.add_tabletstateinfo(); - CopyTabletStateInfo(tabletStateInfo, it->second, request); + Copy(tabletStateInfo, it->second, request); } } } } - } else if (request.groupby() == "Type,State") { // the only supported group-by for now + } else if (request.groupby() == "Type,State" || request.groupby() == "NodeId,Type,State") { // the only supported group-by for now std::unordered_map, NKikimrWhiteboard::TTabletStateInfo> stateGroupBy; for (const auto& [id, stateInfo] : TabletStateInfo) { @@ -761,7 +883,7 @@ class TNodeWhiteboardService : public TActorBootstrapped for (const auto& pr : NodeStateInfo) { if (pr.second.GetChangeTime() >= changedSince) { NKikimrWhiteboard::TNodeStateInfo &nodeStateInfo = *record.AddNodeStateInfo(); - nodeStateInfo.CopyFrom(pr.second); + Copy(nodeStateInfo, pr.second, request); } } response->Record.SetResponseTime(ctx.Now().MilliSeconds()); @@ -792,7 +914,7 @@ class TNodeWhiteboardService : public TActorBootstrapped for (const auto& pr : PDiskStateInfo) { if (pr.second.GetChangeTime() >= changedSince) { NKikimrWhiteboard::TPDiskStateInfo &pDiskStateInfo = *record.AddPDiskStateInfo(); - pDiskStateInfo.CopyFrom(pr.second); + Copy(pDiskStateInfo, pr.second, request); } } response->Record.SetResponseTime(ctx.Now().MilliSeconds()); @@ -816,7 +938,7 @@ class TNodeWhiteboardService : public TActorBootstrapped for (const auto& pr : VDiskStateInfo) { if (pr.second.GetChangeTime() >= changedSince) { NKikimrWhiteboard::TVDiskStateInfo &vDiskStateInfo = *record.AddVDiskStateInfo(); - vDiskStateInfo.CopyFrom(pr.second); + Copy(vDiskStateInfo, pr.second, request); } } response->Record.SetResponseTime(ctx.Now().MilliSeconds()); @@ -831,7 +953,7 @@ class TNodeWhiteboardService : public TActorBootstrapped for (const auto& pr : BSGroupStateInfo) { if (pr.second.GetChangeTime() >= changedSince) { NKikimrWhiteboard::TBSGroupStateInfo &bSGroupStateInfo = *record.AddBSGroupStateInfo(); - bSGroupStateInfo.CopyFrom(pr.second); + Copy(bSGroupStateInfo, pr.second, request); } } response->Record.SetResponseTime(ctx.Now().MilliSeconds()); @@ -845,7 +967,7 @@ class TNodeWhiteboardService : public TActorBootstrapped auto& record = response->Record; if (SystemStateInfo.GetChangeTime() >= changedSince) { NKikimrWhiteboard::TSystemStateInfo &systemStateInfo = *record.AddSystemStateInfo(); - systemStateInfo.CopyFrom(SystemStateInfo); + Copy(systemStateInfo, SystemStateInfo, request); } response->Record.SetResponseTime(ctx.Now().MilliSeconds()); ctx.Send(ev->Sender, response.Release(), 0, ev->Cookie); diff --git a/ydb/core/testlib/test_client.cpp b/ydb/core/testlib/test_client.cpp index 7c25835169a3..30962870e8e6 100644 --- a/ydb/core/testlib/test_client.cpp +++ b/ydb/core/testlib/test_client.cpp @@ -562,7 +562,12 @@ namespace Tests { NKikimrBlobStorage::TDefineHostConfig hostConfig; hostConfig.SetHostConfigId(nodeId); - TString path = TStringBuilder() << Runtime->GetTempDir() << "pdisk_1.dat"; + TString path; + if (Settings->UseSectorMap) { + path ="SectorMap:test-client[:2000]"; + } else { + path = TStringBuilder() << Runtime->GetTempDir() << "pdisk_1.dat"; + } hostConfig.AddDrive()->SetPath(path); Cerr << "test_client.cpp: SetPath # " << path << Endl; bsConfigureRequest->Record.MutableRequest()->AddCommand()->MutableDefineHostConfig()->CopyFrom(hostConfig); diff --git a/ydb/core/testlib/test_client.h b/ydb/core/testlib/test_client.h index 6723164f58f1..133f0fe77d39 100644 --- a/ydb/core/testlib/test_client.h +++ b/ydb/core/testlib/test_client.h @@ -156,6 +156,8 @@ namespace Tests { NYql::IYtGateway::TPtr YtGateway; bool InitializeFederatedQuerySetupFactory = false; TString ServerCertFilePath; + bool Verbose = true; + bool UseSectorMap = false; std::function CreateTicketParser = NKikimr::CreateTicketParser; std::shared_ptr GrpcServiceFactory; @@ -205,6 +207,8 @@ namespace Tests { TServerSettings& SetComputationFactory(NMiniKQL::TComputationNodeFactory computationFactory) { ComputationFactory = std::move(computationFactory); return *this; } TServerSettings& SetYtGateway(NYql::IYtGateway::TPtr ytGateway) { YtGateway = std::move(ytGateway); return *this; } TServerSettings& SetInitializeFederatedQuerySetupFactory(bool value) { InitializeFederatedQuerySetupFactory = value; return *this; } + TServerSettings& SetVerbose(bool value) { Verbose = value; return *this; } + TServerSettings& SetUseSectorMap(bool value) { UseSectorMap = value; return *this; } TServerSettings& SetPersQueueGetReadSessionsInfoWorkerFactory( std::shared_ptr factory ) { diff --git a/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp b/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp index 39673d15b071..deb7be3d89e9 100644 --- a/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp +++ b/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp @@ -17,6 +17,9 @@ #include #include +#include + +#include namespace NKikimr { @@ -32,6 +35,16 @@ enum class EInitialEviction { namespace { +Aws::SDKOptions Options; + +Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); +} + +Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); +} + static const std::vector testYdbSchema = TTestSchema::YdbSchema(); static const std::vector testYdbPk = TTestSchema::YdbPkSchema(); diff --git a/ydb/core/tx/columnshard/ut_schema/ya.make b/ydb/core/tx/columnshard/ut_schema/ya.make index 35d906ee2055..d67c0d2ad5b8 100644 --- a/ydb/core/tx/columnshard/ut_schema/ya.make +++ b/ydb/core/tx/columnshard/ut_schema/ya.make @@ -18,6 +18,7 @@ PEERDIR( library/cpp/getopt library/cpp/regex/pcre library/cpp/svnversion + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core ydb/core/testlib/default ydb/core/tx/columnshard/hooks/abstract ydb/core/tx/columnshard/hooks/testing diff --git a/ydb/core/tx/datashard/change_sender_async_index.cpp b/ydb/core/tx/datashard/change_sender_async_index.cpp index 38492b20728e..106d03406b58 100644 --- a/ydb/core/tx/datashard/change_sender_async_index.cpp +++ b/ydb/core/tx/datashard/change_sender_async_index.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -435,16 +436,6 @@ class TAsyncIndexChangeSenderMain return Check(&TSchemeCacheHelpers::CheckEntryKind, &TThis::LogWarnAndRetry, entry, expected); } - static TVector MakePartitionIds(const TVector& partitions) { - TVector result(Reserve(partitions.size())); - - for (const auto& partition : partitions) { - result.push_back(partition.ShardId); // partition = shard - } - - return result; - } - /// ResolveUserTable void ResolveUserTable() { @@ -611,6 +602,11 @@ class TAsyncIndexChangeSenderMain return; } + if (IndexTableVersion && IndexTableVersion == entry.Self->Info.GetVersion().GetGeneralVersion()) { + CreateSenders(); + return Become(&TThis::StateMain); + } + TagMap.clear(); TVector keyColumnTypes; @@ -692,11 +688,9 @@ class TAsyncIndexChangeSenderMain return Retry(); } - const bool versionChanged = !IndexTableVersion || IndexTableVersion != entry.GeneralVersion; IndexTableVersion = entry.GeneralVersion; - KeyDesc = std::move(entry.KeyDescription); - CreateSenders(MakePartitionIds(KeyDesc->GetPartitions()), versionChanged); + CreateSenders(NChangeExchange::MakePartitionIds(KeyDesc->GetPartitions())); Become(&TThis::StateMain); } diff --git a/ydb/core/tx/datashard/change_sender_cdc_stream.cpp b/ydb/core/tx/datashard/change_sender_cdc_stream.cpp index 5300357c24cd..ecf916263b6d 100644 --- a/ydb/core/tx/datashard/change_sender_cdc_stream.cpp +++ b/ydb/core/tx/datashard/change_sender_cdc_stream.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -300,45 +300,6 @@ class TCdcChangeSenderMain , public NChangeExchange::ISenderFactory , private NSchemeCache::TSchemeCacheHelpers { - struct TPQPartitionInfo { - ui32 PartitionId; - ui64 ShardId; - TPartitionKeyRange KeyRange; - - struct TLess { - TConstArrayRef Schema; - - TLess(const TVector& schema) - : Schema(schema) - { - } - - bool operator()(const TPQPartitionInfo& lhs, const TPQPartitionInfo& rhs) const { - Y_ABORT_UNLESS(lhs.KeyRange.ToBound || rhs.KeyRange.ToBound); - - if (!lhs.KeyRange.ToBound) { - return false; - } - - if (!rhs.KeyRange.ToBound) { - return true; - } - - Y_ABORT_UNLESS(lhs.KeyRange.ToBound && rhs.KeyRange.ToBound); - - const int compares = CompareTypedCellVectors( - lhs.KeyRange.ToBound->GetCells().data(), - rhs.KeyRange.ToBound->GetCells().data(), - Schema.data(), Schema.size() - ); - - return (compares < 0); - } - - }; // TLess - - }; // TPQPartitionInfo - TStringBuf GetLogPrefix() const { if (!LogPrefix) { LogPrefix = TStringBuilder() @@ -430,16 +391,6 @@ class TCdcChangeSenderMain return false; } - static TVector MakePartitionIds(const TVector& partitions) { - TVector result(Reserve(partitions.size())); - - for (const auto& partition : partitions) { - result.push_back(partition.ShardId); - } - - return result; - } - /// ResolveCdcStream void ResolveCdcStream() { @@ -561,77 +512,27 @@ class TCdcChangeSenderMain return; } - const auto& pqDesc = entry.PQGroupInfo->Description; - const auto& pqConfig = pqDesc.GetPQTabletConfig(); - - TVector schema; - PartitionToShard.clear(); - - schema.reserve(pqConfig.PartitionKeySchemaSize()); - for (const auto& keySchema : pqConfig.GetPartitionKeySchema()) { - // TODO: support pg types - schema.push_back(NScheme::TTypeInfo(keySchema.GetTypeId())); + const auto topicVersion = entry.Self->Info.GetVersion().GetGeneralVersion(); + if (TopicVersion && TopicVersion == topicVersion) { + CreateSenders(); + return Become(&TThis::StateMain); } - TSet partitions(schema); - THashSet shards; - - for (const auto& partition : pqDesc.GetPartitions()) { - const auto partitionId = partition.GetPartitionId(); - const auto shardId = partition.GetTabletId(); - - PartitionToShard.emplace(partitionId, shardId); - - auto keyRange = TPartitionKeyRange::Parse(partition.GetKeyRange()); - Y_ABORT_UNLESS(!keyRange.FromBound || keyRange.FromBound->GetCells().size() == schema.size()); - Y_ABORT_UNLESS(!keyRange.ToBound || keyRange.ToBound->GetCells().size() == schema.size()); - - partitions.insert({partitionId, shardId, std::move(keyRange)}); - shards.insert(shardId); - } - - // used to validate - bool isFirst = true; - const TPQPartitionInfo* prev = nullptr; - - TVector partitioning; - partitioning.reserve(partitions.size()); - for (const auto& cur : partitions) { - if (isFirst) { - isFirst = false; - Y_ABORT_UNLESS(!cur.KeyRange.FromBound.Defined()); - } else { - Y_ABORT_UNLESS(cur.KeyRange.FromBound.Defined()); - Y_ABORT_UNLESS(prev); - Y_ABORT_UNLESS(prev->KeyRange.ToBound.Defined()); - // TODO: compare cells - } - - auto& part = partitioning.emplace_back(cur.PartitionId); // TODO: double-check that it is right partitioning - - if (cur.KeyRange.ToBound) { - part.Range = NKikimr::TKeyDesc::TPartitionRangeInfo{ - .EndKeyPrefix = *cur.KeyRange.ToBound, - }; - } else { - part.Range = NKikimr::TKeyDesc::TPartitionRangeInfo{}; - } + TopicVersion = topicVersion; - prev = &cur; - } + const auto& pqDesc = entry.PQGroupInfo->Description; - if (prev) { - Y_ABORT_UNLESS(!prev->KeyRange.ToBound.Defined()); + PartitionToShard.clear(); + for (const auto& partition : pqDesc.GetPartitions()) { + PartitionToShard.emplace(partition.GetPartitionId(), partition.GetTabletId()); } - const auto topicVersion = entry.Self->Info.GetVersion().GetGeneralVersion(); - const bool versionChanged = !TopicVersion || TopicVersion != topicVersion; - TopicVersion = topicVersion; - - KeyDesc = NKikimr::TKeyDesc::CreateMiniKeyDesc(schema); - KeyDesc->Partitioning = std::make_shared>(std::move(partitioning)); + Y_ABORT_UNLESS(entry.PQGroupInfo->Schema); + KeyDesc = NKikimr::TKeyDesc::CreateMiniKeyDesc(entry.PQGroupInfo->Schema); + Y_ABORT_UNLESS(entry.PQGroupInfo->Partitioning); + KeyDesc->Partitioning = std::make_shared>(entry.PQGroupInfo->Partitioning); - CreateSenders(MakePartitionIds(*KeyDesc->Partitioning), versionChanged); + CreateSenders(NChangeExchange::MakePartitionIds(*KeyDesc->Partitioning)); Become(&TThis::StateMain); } diff --git a/ydb/core/tx/datashard/datashard_ut_write.cpp b/ydb/core/tx/datashard/datashard_ut_write.cpp index 34fbff3263a5..03c6b3d79132 100644 --- a/ydb/core/tx/datashard/datashard_ut_write.cpp +++ b/ydb/core/tx/datashard/datashard_ut_write.cpp @@ -1596,5 +1596,106 @@ Y_UNIT_TEST_SUITE(DataShardWrite) { } } + Y_UNIT_TEST(PreparedDistributedWritePageFault) { + TPortManager pm; + TServerSettings serverSettings(pm.GetPort(2134)); + serverSettings.SetDomainName("Root") + .SetUseRealThreads(false) + .SetEnableDataShardVolatileTransactions(false); + + auto [runtime, server, sender] = TestCreateServer(serverSettings); + + TDisableDataShardLogBatching disableDataShardLogBatching; + + // Use a policy without levels and very small page sizes, effectively making each row on its own page + NLocalDb::TCompactionPolicyPtr policy = NLocalDb::CreateDefaultTablePolicy(); + policy->MinDataPageSize = 1; + + auto opts = TShardedTableOptions() + .Columns({{"key", "Int32", true, false}, + {"value", "Int32", false, false}}) + .Policy(policy.Get()); + const auto& columns = opts.Columns_; + auto [shards, tableId] = CreateShardedTable(server, sender, "/Root", "table", opts); + UNIT_ASSERT_VALUES_EQUAL(shards.size(), 1u); + + const ui64 coordinator = ChangeStateStorage(Coordinator, server->GetSettings().Domain); + + const ui64 lockTxId1 = 1234567890001; + const ui64 lockNodeId = runtime.GetNodeId(0); + NLongTxService::TLockHandle lockHandle1(lockTxId1, runtime.GetActorSystem(0)); + + auto shard1 = shards.at(0); + NKikimrDataEvents::TLock lock1shard1; + + // 1. Make an uncommitted write (lock1 shard1) + { + Cerr << "... making an uncommmited write to " << shard1 << Endl; + auto req = MakeWriteRequestOneKeyValue( + std::nullopt, + NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE, + NKikimrDataEvents::TEvWrite::TOperation::OPERATION_UPSERT, + tableId, + columns, + 1, 11); + req->SetLockId(lockTxId1, lockNodeId); + auto result = Write(runtime, sender, shard1, std::move(req)); + UNIT_ASSERT_VALUES_EQUAL(result.GetTxLocks().size(), 1u); + lock1shard1 = result.GetTxLocks().at(0); + UNIT_ASSERT_C(lock1shard1.GetCounter() < 1000, "Unexpected lock in the result: " << lock1shard1.ShortDebugString()); + } + + // 2. Compact and reboot the tablet + Cerr << "... compacting shard " << shard1 << Endl; + CompactTable(runtime, shard1, tableId, false); + Cerr << "... rebooting shard " << shard1 << Endl; + RebootTablet(runtime, shard1, sender); + runtime.SimulateSleep(TDuration::Seconds(1)); + + // 3. Prepare a distributed write (single shard for simplicity) + ui64 txId1 = 1234567890011; + auto tx1sender = runtime.AllocateEdgeActor(); + { + auto req1 = MakeWriteRequestOneKeyValue( + txId1, + NKikimrDataEvents::TEvWrite::MODE_PREPARE, + NKikimrDataEvents::TEvWrite::TOperation::OPERATION_UPSERT, + tableId, + columns, + 1, 22); + req1->Record.MutableLocks()->SetOp(NKikimrDataEvents::TKqpLocks::Commit); + + Cerr << "... preparing tx1 at " << shard1 << Endl; + auto res1 = Write(runtime, tx1sender, shard1, std::move(req1)); + + // Reboot, making sure tx is only loaded after it's planned + // This causes tx to skip conflicts cache and go to execution + // The first attempt to execute will page fault looking for conflicts + // Tx will be released, and will trigger the bug on restore + Cerr << "... rebooting shard " << shard1 << Endl; + RebootTablet(runtime, shard1, sender); + runtime.SimulateSleep(TDuration::Seconds(1)); + + ui64 minStep = res1.GetMinStep(); + ui64 maxStep = res1.GetMaxStep(); + + Cerr << "... planning tx1 at " << coordinator << Endl; + SendProposeToCoordinator( + runtime, tx1sender, { shard1 }, { + .TxId = txId1, + .Coordinator = coordinator, + .MinStep = minStep, + .MaxStep = maxStep, + }); + } + + // 4. Check tx1 reply (it must succeed) + { + Cerr << "... waiting for tx1 result" << Endl; + auto ev = runtime.GrabEdgeEventRethrow(tx1sender); + UNIT_ASSERT_VALUES_EQUAL(ev->Get()->Record.GetStatus(), NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); + } + } + } // Y_UNIT_TEST_SUITE } // namespace NKikimr diff --git a/ydb/core/tx/datashard/datashard_write_operation.cpp b/ydb/core/tx/datashard/datashard_write_operation.cpp index 5ec4e5e92bac..0d67c7f02350 100644 --- a/ydb/core/tx/datashard/datashard_write_operation.cpp +++ b/ydb/core/tx/datashard/datashard_write_operation.cpp @@ -416,8 +416,9 @@ TValidatedWriteTx::TPtr TWriteOperation::BuildWriteTx(TDataShard* self) void TWriteOperation::ReleaseTxData(NTabletFlatExecutor::TTxMemoryProviderBase& provider) { ReleasedTxDataSize = provider.GetMemoryLimit() + provider.GetRequestedMemory(); - if (!WriteTx || IsTxDataReleased()) + if (!WriteTx || WriteTx->GetIsReleased()) { return; + } WriteTx->ReleaseTxData(); // Immediate transactions have no body stored. diff --git a/ydb/core/tx/datashard/import_s3.cpp b/ydb/core/tx/datashard/import_s3.cpp index ba7227a7a74c..655ee80172ab 100644 --- a/ydb/core/tx/datashard/import_s3.cpp +++ b/ydb/core/tx/datashard/import_s3.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/ydb/core/tx/datashard/ya.make b/ydb/core/tx/datashard/ya.make index b16535504346..6cfc69901adb 100644 --- a/ydb/core/tx/datashard/ya.make +++ b/ydb/core/tx/datashard/ya.make @@ -244,7 +244,6 @@ PEERDIR( ydb/core/formats ydb/core/io_formats/ydb_dump ydb/core/kqp/runtime - ydb/core/persqueue/partition_key_range ydb/core/persqueue/writer ydb/core/protos ydb/core/tablet diff --git a/ydb/core/tx/replication/service/table_writer_impl.h b/ydb/core/tx/replication/service/table_writer_impl.h index 1fd77232fb2d..475aa3c351a2 100644 --- a/ydb/core/tx/replication/service/table_writer_impl.h +++ b/ydb/core/tx/replication/service/table_writer_impl.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -278,16 +279,6 @@ class TLocalTableWriter return Check(&TSchemeCacheHelpers::CheckEntryKind, &TThis::LogCritAndLeave, entry, expected); } - static TVector MakePartitionIds(const TVector& partitions) { - TVector result(::Reserve(partitions.size())); - - for (const auto& partition : partitions) { - result.push_back(partition.ShardId); - } - - return result; - } - void Registered(TActorSystem*, const TActorId&) override { this->ChangeServer = this->SelfId(); } @@ -348,6 +339,12 @@ class TLocalTableWriter return; } + if (TableVersion && TableVersion == entry.Self->Info.GetVersion().GetGeneralVersion()) { + Y_ABORT_UNLESS(Initialized); + Resolving = false; + return this->CreateSenders(); + } + auto schema = MakeIntrusive(); if (entry.Self && entry.Self->Info.HasVersion()) { schema->Version = entry.Self->Info.GetVersion().GetTableSchemaVersion(); @@ -415,11 +412,9 @@ class TLocalTableWriter return LogWarnAndRetry("Empty partitions"); } - const bool versionChanged = !TableVersion || TableVersion != entry.GeneralVersion; TableVersion = entry.GeneralVersion; - KeyDesc = std::move(entry.KeyDescription); - this->CreateSenders(MakePartitionIds(KeyDesc->GetPartitions()), versionChanged); + this->CreateSenders(NChangeExchange::MakePartitionIds(KeyDesc->GetPartitions())); if (!Initialized) { this->Send(Worker, new TEvWorker::TEvHandshake()); diff --git a/ydb/core/tx/scheme_board/cache.cpp b/ydb/core/tx/scheme_board/cache.cpp index b521e7ed7e87..0c0c2061a3c3 100644 --- a/ydb/core/tx/scheme_board/cache.cpp +++ b/ydb/core/tx/scheme_board/cache.cpp @@ -13,9 +13,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -26,6 +26,8 @@ #include #include #include +#include + #include #include @@ -976,6 +978,62 @@ class TSchemeCache: public TMonitorableActor { return partitions; } + static void FillTopicPartitioning( + const NKikimrSchemeOp::TPersQueueGroupDescription& pqDesc, + TVector& schema, + TVector& partitioning) + { + const auto& pqConfig = pqDesc.GetPQTabletConfig(); + if (pqConfig.GetPartitionKeySchema().empty()) { + return; + } + + schema.reserve(pqConfig.PartitionKeySchemaSize()); + for (const auto& keySchema : pqConfig.GetPartitionKeySchema()) { + // TODO: support pg types + schema.push_back(NScheme::TTypeInfo(keySchema.GetTypeId())); + } + + partitioning.reserve(pqDesc.PartitionsSize()); + for (const auto& partition : pqDesc.GetPartitions()) { + auto keyRange = NPQ::TPartitionKeyRange::Parse(partition.GetKeyRange()); + Y_ABORT_UNLESS(!keyRange.FromBound || keyRange.FromBound->GetCells().size() == schema.size()); + Y_ABORT_UNLESS(!keyRange.ToBound || keyRange.ToBound->GetCells().size() == schema.size()); + + auto& info = partitioning.emplace_back(partition.GetPartitionId()); + if (keyRange.ToBound) { + info.Range = NKikimr::TKeyDesc::TPartitionRangeInfo{ + .EndKeyPrefix = *keyRange.ToBound, + }; + } else { + info.Range = NKikimr::TKeyDesc::TPartitionRangeInfo{}; + } + } + + Sort(partitioning.begin(), partitioning.end(), [&schema](const auto& lhs, const auto& rhs) { + Y_ABORT_UNLESS(lhs.Range && rhs.Range); + Y_ABORT_UNLESS(lhs.Range->EndKeyPrefix || rhs.Range->EndKeyPrefix); + + if (!lhs.Range->EndKeyPrefix) { + return false; + } + + if (!rhs.Range->EndKeyPrefix) { + return true; + } + + Y_ABORT_UNLESS(lhs.Range->EndKeyPrefix && rhs.Range->EndKeyPrefix); + + const int compares = CompareTypedCellVectors( + lhs.Range->EndKeyPrefix.GetCells().data(), + rhs.Range->EndKeyPrefix.GetCells().data(), + schema.data(), schema.size() + ); + + return (compares < 0); + }); + } + bool IsSysTable() const { return Kind == TNavigate::KindTable && PathId.OwnerId == TSysTables::SysSchemeShard; } @@ -1484,6 +1542,7 @@ class TSchemeCache: public TMonitorableActor { if (Created) { NPQ::Migrate(*pathDesc.MutablePersQueueGroup()->MutablePQTabletConfig()); FillInfo(Kind, PQGroupInfo, std::move(*pathDesc.MutablePersQueueGroup())); + FillTopicPartitioning(PQGroupInfo->Description, PQGroupInfo->Schema, PQGroupInfo->Partitioning); } break; case NKikimrSchemeOp::EPathTypeCdcStream: diff --git a/ydb/core/tx/scheme_board/ya.make b/ydb/core/tx/scheme_board/ya.make index 22f66c217f09..ffb2121ca4c9 100644 --- a/ydb/core/tx/scheme_board/ya.make +++ b/ydb/core/tx/scheme_board/ya.make @@ -4,6 +4,7 @@ PEERDIR( ydb/library/actors/core ydb/core/base ydb/core/mon + ydb/core/persqueue/partition_key_range ydb/core/protos ydb/core/sys_view/common ydb/core/tx/scheme_cache diff --git a/ydb/core/tx/scheme_cache/scheme_cache.h b/ydb/core/tx/scheme_cache/scheme_cache.h index bc618b2a96cf..5a819b8fe896 100644 --- a/ydb/core/tx/scheme_cache/scheme_cache.h +++ b/ydb/core/tx/scheme_cache/scheme_cache.h @@ -190,6 +190,8 @@ struct TSchemeCacheNavigate { struct TPQGroupInfo : public TAtomicRefCount { EKind Kind = KindUnknown; NKikimrSchemeOp::TPersQueueGroupDescription Description; + TVector Schema; + TVector Partitioning; }; struct TRtmrVolumeInfo : public TAtomicRefCount { diff --git a/ydb/core/tx/schemeshard/schemeshard__init.cpp b/ydb/core/tx/schemeshard/schemeshard__init.cpp index 5a6f2d0a7bf7..2226cc2576c3 100644 --- a/ydb/core/tx/schemeshard/schemeshard__init.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__init.cpp @@ -1906,6 +1906,9 @@ struct TSchemeShard::TTxInit : public TTransactionBase { auto& view = Self->Views[pathId] = new TViewInfo(); view->AlterVersion = rowset.GetValue(); view->QueryText = rowset.GetValue(); + Y_PROTOBUF_SUPPRESS_NODISCARD view->CapturedContext.ParseFromString( + rowset.GetValue() + ); Self->IncrementPathDbRefCount(pathId); if (!rowset.Next()) { diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_alter_extsubdomain.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_alter_extsubdomain.cpp index 5b67ca14030c..97721f819e00 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_alter_extsubdomain.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_alter_extsubdomain.cpp @@ -936,21 +936,23 @@ class TAlterExtSubDomain: public TSubOperation { // Create or derive alter. // (We could have always created new alter from a current subdomainInfo but // we need to take into account possible version increase from CreateHive suboperation.) - auto createAlterFrom = [&inputSettings, &delta](auto prototype) { + auto createAlterFrom = [&inputSettings](auto prototype, const TStoragePools& additionalPools) { return MakeIntrusive( *prototype, inputSettings.GetPlanResolution(), inputSettings.GetTimeCastBucketsPerMediator(), - delta.StoragePoolsAdded + additionalPools ); }; TSubDomainInfo::TPtr alter = [&delta, &subdomainInfo, &createAlterFrom, &context]() { if (delta.AddExternalHive && context.SS->EnableAlterDatabaseCreateHiveFirst) { Y_ABORT_UNLESS(subdomainInfo->GetAlter()); - return createAlterFrom(subdomainInfo->GetAlter()); + //NOTE: existing alter already has all storage pools that combined operation wanted to add, + // should not add them second time when deriving alter from alter + return createAlterFrom(subdomainInfo->GetAlter(), {}); } else { Y_ABORT_UNLESS(!subdomainInfo->GetAlter()); - return createAlterFrom(subdomainInfo); + return createAlterFrom(subdomainInfo, delta.StoragePoolsAdded); } }(); diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_create_view.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_create_view.cpp index e4f6e69922ba..0e172572f778 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_create_view.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_create_view.cpp @@ -48,7 +48,7 @@ class TPropose: public TSubOperationState { Y_ABORT_UNLESS(txState->TxType == TTxState::TxCreateView); context.SS->TabletCounters->Simple()[COUNTER_VIEW_COUNT].Add(1); - + const auto pathId = txState->TargetPathId; auto path = TPath::Init(pathId, context.SS); @@ -68,6 +68,7 @@ TViewInfo::TPtr CreateView(const NKikimrSchemeOp::TViewDescription& desc) { TViewInfo::TPtr viewInfo = new TViewInfo; viewInfo->AlterVersion = 1; viewInfo->QueryText = desc.GetQueryText(); + viewInfo->CapturedContext = desc.GetCapturedContext(); return viewInfo; } @@ -109,7 +110,7 @@ class TCreateView: public TSubOperation { const auto& viewDescription = Transaction.GetCreateView(); const TString& name = viewDescription.GetName(); - + LOG_N("TCreateView Propose" << ", path: " << parentPathStr << "/" << name << ", opId: " << OperationId diff --git a/ydb/core/tx/schemeshard/schemeshard_export_flow_proposals.cpp b/ydb/core/tx/schemeshard/schemeshard_export_flow_proposals.cpp index 4af5a86d53ea..d6fca52aecaf 100644 --- a/ydb/core/tx/schemeshard/schemeshard_export_flow_proposals.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_export_flow_proposals.cpp @@ -76,6 +76,7 @@ static NKikimrSchemeOp::TPathDescription GetTableDescription(TSchemeShard* ss, c opts.SetReturnPartitioningInfo(false); opts.SetReturnPartitionConfig(true); opts.SetReturnBoundaries(true); + opts.SetReturnIndexTableBoundaries(true); auto desc = DescribePath(ss, TlsActivationContext->AsActorContext(), pathId, opts); auto record = desc->GetRecord(); diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.cpp b/ydb/core/tx/schemeshard/schemeshard_impl.cpp index 6b9c53f10ac4..e1688307eb7a 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_impl.cpp @@ -2955,7 +2955,9 @@ void TSchemeShard::PersistView(NIceDb::TNiceDb &db, TPathId pathId) { db.Table().Key(pathId.LocalPathId).Update( NIceDb::TUpdate{viewInfo->AlterVersion}, - NIceDb::TUpdate{viewInfo->QueryText}); + NIceDb::TUpdate{viewInfo->QueryText}, + NIceDb::TUpdate{viewInfo->CapturedContext.SerializeAsString()} + ); } void TSchemeShard::PersistRemoveView(NIceDb::TNiceDb& db, TPathId pathId) { @@ -4267,6 +4269,10 @@ ui64 TSchemeShard::GetAliveChildren(TPathElement::TPtr pathEl, const std::option Y_ABORT_UNLESS(PathsById.contains(pathId)); auto childPath = PathsById.at(pathId); + if (childPath->Dropped()) { + continue; + } + count += ui64(childPath->PathType == *type); } diff --git a/ydb/core/tx/schemeshard/schemeshard_info_types.h b/ydb/core/tx/schemeshard/schemeshard_info_types.h index fc752993a471..8bbdca2ac540 100644 --- a/ydb/core/tx/schemeshard/schemeshard_info_types.h +++ b/ydb/core/tx/schemeshard/schemeshard_info_types.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -3280,6 +3281,7 @@ struct TViewInfo : TSimpleRefCount { ui64 AlterVersion = 0; TString QueryText; + NYql::NProto::TTranslationSettings CapturedContext; }; struct TResourcePoolInfo : TSimpleRefCount { diff --git a/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp b/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp index 7b39e9113433..12e6a238570e 100644 --- a/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp @@ -72,6 +72,78 @@ static void FillTableStats(NKikimrSchemeOp::TPathDescription& pathDescription, c FillTableMetrics(pathDescription.MutableTabletMetrics(), stats); } +static void FillColumns( + const TTableInfo& tableInfo, + google::protobuf::RepeatedPtrField& out +) { + bool familyNamesBuilt = false; + THashMap familyNames; + + out.Reserve(tableInfo.Columns.size()); + for (const auto& col : tableInfo.Columns) { + const auto& cinfo = col.second; + if (cinfo.IsDropped()) + continue; + + auto* colDescr = out.Add(); + colDescr->SetName(cinfo.Name); + colDescr->SetType(NScheme::TypeName(cinfo.PType, cinfo.PTypeMod)); + auto columnType = NScheme::ProtoColumnTypeFromTypeInfoMod(cinfo.PType, cinfo.PTypeMod); + colDescr->SetTypeId(columnType.TypeId); + if (columnType.TypeInfo) { + *colDescr->MutableTypeInfo() = *columnType.TypeInfo; + } + colDescr->SetId(cinfo.Id); + colDescr->SetNotNull(cinfo.NotNull); + + if (cinfo.Family != 0) { + colDescr->SetFamily(cinfo.Family); + + if (!familyNamesBuilt) { + for (const auto& family : tableInfo.PartitionConfig().GetColumnFamilies()) { + if (family.HasName() && family.HasId()) { + familyNames[family.GetId()] = family.GetName(); + } + } + familyNamesBuilt = true; + } + + auto it = familyNames.find(cinfo.Family); + if (it != familyNames.end() && !it->second.empty()) { + colDescr->SetFamilyName(it->second); + } + } + + colDescr->SetIsBuildInProgress(cinfo.IsBuildInProgress); + + switch (cinfo.DefaultKind) { + case ETableColumnDefaultKind::None: + break; + case ETableColumnDefaultKind::FromSequence: + colDescr->SetDefaultFromSequence(cinfo.DefaultValue); + break; + case ETableColumnDefaultKind::FromLiteral: + Y_ABORT_UNLESS(colDescr->MutableDefaultFromLiteral()->ParseFromString( + cinfo.DefaultValue)); + break; + } + } +} + +static void FillKeyColumns( + const TTableInfo& tableInfo, + google::protobuf::RepeatedPtrField& names, + google::protobuf::RepeatedField& ids +) { + Y_ABORT_UNLESS(!tableInfo.KeyColumnIds.empty()); + names.Reserve(tableInfo.KeyColumnIds.size()); + ids.Reserve(tableInfo.KeyColumnIds.size()); + for (ui32 keyColId : tableInfo.KeyColumnIds) { + *names.Add() = tableInfo.Columns.at(keyColId).Name; + *ids.Add() = keyColId; + } +} + void TPathDescriber::FillPathDescr(NKikimrSchemeOp::TDirEntry* descr, TPathElement::TPtr pathEl, TPathElement::EPathSubType subType) { FillChildDescr(descr, pathEl); @@ -292,6 +364,7 @@ void TPathDescriber::DescribeTable(const TActorContext& ctx, TPathId pathId, TPa bool returnBoundaries = false; bool returnRangeKey = true; bool returnSetVal = Params.GetOptions().GetReturnSetVal(); + bool returnIndexTableBoundaries = Params.GetOptions().GetReturnIndexTableBoundaries(); if (Params.HasOptions()) { returnConfig = Params.GetOptions().GetReturnPartitionConfig(); returnPartitioning = Params.GetOptions().GetReturnPartitioningInfo(); @@ -416,7 +489,9 @@ void TPathDescriber::DescribeTable(const TActorContext& ctx, TPathId pathId, TPa switch (childPath->PathType) { case NKikimrSchemeOp::EPathTypeTableIndex: - Self->DescribeTableIndex(childPathId, childName, returnConfig, false, *entry->AddTableIndexes()); + Self->DescribeTableIndex( + childPathId, childName, returnConfig, returnIndexTableBoundaries, *entry->AddTableIndexes() + ); break; case NKikimrSchemeOp::EPathTypeCdcStream: Self->DescribeCdcStream(childPathId, childName, *entry->AddCdcStreams()); @@ -961,6 +1036,7 @@ void TPathDescriber::DescribeView(const TActorContext&, TPathId pathId, TPathEle PathIdFromPathId(pathId, entry->MutablePathId()); entry->SetVersion(viewInfo->AlterVersion); entry->SetQueryText(viewInfo->QueryText); + *entry->MutableCapturedContext() = viewInfo->CapturedContext; } void TPathDescriber::DescribeResourcePool(TPathId pathId, TPathElement::TPtr pathEl) { @@ -1174,67 +1250,10 @@ void TSchemeShard::DescribeTable( ) const { Y_UNUSED(typeRegistry); - THashMap familyNames; - bool familyNamesBuilt = false; entry->SetTableSchemaVersion(tableInfo->AlterVersion); - entry->MutableColumns()->Reserve(tableInfo->Columns.size()); - for (auto col : tableInfo->Columns) { - const auto& cinfo = col.second; - if (cinfo.IsDropped()) - continue; - - auto colDescr = entry->AddColumns(); - colDescr->SetName(cinfo.Name); - colDescr->SetType(NScheme::TypeName(cinfo.PType, cinfo.PTypeMod)); - auto columnType = NScheme::ProtoColumnTypeFromTypeInfoMod(cinfo.PType, cinfo.PTypeMod); - colDescr->SetTypeId(columnType.TypeId); - if (columnType.TypeInfo) { - *colDescr->MutableTypeInfo() = *columnType.TypeInfo; - } - colDescr->SetId(cinfo.Id); - colDescr->SetNotNull(cinfo.NotNull); - - if (cinfo.Family != 0) { - colDescr->SetFamily(cinfo.Family); - - if (!familyNamesBuilt) { - for (const auto& family : tableInfo->PartitionConfig().GetColumnFamilies()) { - if (family.HasName() && family.HasId()) { - familyNames[family.GetId()] = family.GetName(); - } - } - familyNamesBuilt = true; - } - - auto it = familyNames.find(cinfo.Family); - if (it != familyNames.end() && !it->second.empty()) { - colDescr->SetFamilyName(it->second); - } - } - - colDescr->SetIsBuildInProgress(cinfo.IsBuildInProgress); - - switch (cinfo.DefaultKind) { - case ETableColumnDefaultKind::None: - break; - case ETableColumnDefaultKind::FromSequence: - colDescr->SetDefaultFromSequence(cinfo.DefaultValue); - break; - case ETableColumnDefaultKind::FromLiteral: - Y_ABORT_UNLESS(colDescr->MutableDefaultFromLiteral()->ParseFromString( - cinfo.DefaultValue)); - break; - } - } - Y_ABORT_UNLESS(!tableInfo->KeyColumnIds.empty()); - - entry->MutableKeyColumnNames()->Reserve(tableInfo->KeyColumnIds.size()); - entry->MutableKeyColumnIds()->Reserve(tableInfo->KeyColumnIds.size()); - for (ui32 keyColId : tableInfo->KeyColumnIds) { - entry->AddKeyColumnNames(tableInfo->Columns[keyColId].Name); - entry->AddKeyColumnIds(keyColId); - } + FillColumns(*tableInfo, *entry->MutableColumns()); + FillKeyColumns(*tableInfo, *entry->MutableKeyColumnNames(), *entry->MutableKeyColumnIds()); if (fillConfig) { FillPartitionConfig(tableInfo->PartitionConfig(), *entry->MutablePartitionConfig()); @@ -1298,6 +1317,9 @@ void TSchemeShard::DescribeTableIndex(const TPathId& pathId, const TString& name FillPartitionConfig(tableInfo->PartitionConfig(), *tableDescription->MutablePartitionConfig()); } if (fillBoundaries) { + // column info is necessary for split boundary type conversion + FillColumns(*tableInfo, *tableDescription->MutableColumns()); + FillKeyColumns(*tableInfo, *tableDescription->MutableKeyColumnNames(), *tableDescription->MutableKeyColumnIds()); FillTableBoundaries(tableDescription->MutableSplitBoundary(), tableInfo); } } diff --git a/ydb/core/tx/schemeshard/schemeshard_schema.h b/ydb/core/tx/schemeshard/schemeshard_schema.h index 57ec8569d776..a44c1adbef61 100644 --- a/ydb/core/tx/schemeshard/schemeshard_schema.h +++ b/ydb/core/tx/schemeshard/schemeshard_schema.h @@ -1785,9 +1785,11 @@ struct Schema : NIceDb::Schema { struct PathId: Column<1, NScheme::NTypeIds::Uint64> { using Type = TLocalPathId; }; struct AlterVersion: Column<2, NScheme::NTypeIds::Uint64> {}; struct QueryText: Column<3, NScheme::NTypeIds::String> {}; + // CapturedContext is a serialized NYql::NProto::TTranslationSettings. + struct CapturedContext: Column<4, NScheme::NTypeIds::String> {}; using TKey = TableKey; - using TColumns = TableColumns; + using TColumns = TableColumns; }; struct BackgroundSessions: Table<109> { diff --git a/ydb/core/tx/schemeshard/ut_backup/ut_backup.cpp b/ydb/core/tx/schemeshard/ut_backup/ut_backup.cpp index 1338f2d8f42d..771b929051c0 100644 --- a/ydb/core/tx/schemeshard/ut_backup/ut_backup.cpp +++ b/ydb/core/tx/schemeshard/ut_backup/ut_backup.cpp @@ -7,9 +7,27 @@ #include #include +#include + +#include + using namespace NSchemeShardUT_Private; using namespace NKikimr::NWrappers::NTestHelpers; +namespace { + +Aws::SDKOptions Options; + +Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); +} + +Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); +} + +} + Y_UNIT_TEST_SUITE(TBackupTests) { using TFillFn = std::function; diff --git a/ydb/core/tx/schemeshard/ut_backup/ya.make b/ydb/core/tx/schemeshard/ut_backup/ya.make index d9ee6dd81405..aac9bc5f9334 100644 --- a/ydb/core/tx/schemeshard/ut_backup/ya.make +++ b/ydb/core/tx/schemeshard/ut_backup/ya.make @@ -20,6 +20,7 @@ IF (NOT OS_WINDOWS) library/cpp/getopt library/cpp/regex/pcre library/cpp/svnversion + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core ydb/core/testlib/default ydb/core/tx ydb/core/tx/schemeshard/ut_helpers diff --git a/ydb/core/tx/schemeshard/ut_export/ut_export.cpp b/ydb/core/tx/schemeshard/ut_export/ut_export.cpp index f2e82ce2c7d7..8d41d7bab562 100644 --- a/ydb/core/tx/schemeshard/ut_export/ut_export.cpp +++ b/ydb/core/tx/schemeshard/ut_export/ut_export.cpp @@ -13,11 +13,25 @@ #include #include +#include + +#include + using namespace NSchemeShardUT_Private; using namespace NKikimr::NWrappers::NTestHelpers; namespace { + Aws::SDKOptions Options; + + Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); + } + + Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); + } + void Run(TTestBasicRuntime& runtime, TTestEnv& env, const TVector& tables, const TString& request, Ydb::StatusIds::StatusCode expectedStatus = Ydb::StatusIds::SUCCESS, const TString& dbName = "/MyRoot", bool serverless = false, const TString& userSID = "", const TString& peerName = "") { @@ -1687,7 +1701,7 @@ partitioning_settings { return ev->Get()->Record .GetTransaction(0).GetOperationType() == NKikimrSchemeOp::ESchemeOpBackup; }; - + THolder delayed; auto prevObserver = runtime.SetObserverFunc([&](TAutoPtr& ev) { if (delayFunc(ev)) { @@ -2083,7 +2097,7 @@ partitioning_settings { min_partitions_count: 10 )")); } - + Y_UNIT_TEST(UserSID) { TTestBasicRuntime runtime; TTestEnv env(runtime); diff --git a/ydb/core/tx/schemeshard/ut_export/ya.make b/ydb/core/tx/schemeshard/ut_export/ya.make index 4d5bf91e2698..c62dc9ea8ebc 100644 --- a/ydb/core/tx/schemeshard/ut_export/ya.make +++ b/ydb/core/tx/schemeshard/ut_export/ya.make @@ -20,6 +20,7 @@ IF (NOT OS_WINDOWS) library/cpp/getopt library/cpp/regex/pcre library/cpp/svnversion + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core ydb/core/testlib/default ydb/core/tx ydb/core/tx/schemeshard/ut_helpers diff --git a/ydb/core/tx/schemeshard/ut_export_reboots_s3/ut_export_reboots_s3.cpp b/ydb/core/tx/schemeshard/ut_export_reboots_s3/ut_export_reboots_s3.cpp index 5e1e42d17ce9..97e34a6d3fa4 100644 --- a/ydb/core/tx/schemeshard/ut_export_reboots_s3/ut_export_reboots_s3.cpp +++ b/ydb/core/tx/schemeshard/ut_export_reboots_s3/ut_export_reboots_s3.cpp @@ -4,10 +4,28 @@ #include +#include + +#include + using namespace NSchemeShardUT_Private; using namespace NSchemeShardUT_Private::NExportReboots; using namespace NKikimr::NWrappers::NTestHelpers; +namespace { + +Aws::SDKOptions Options; + +Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); +} + +Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); +} + +} + Y_UNIT_TEST_SUITE(TExportToS3WithRebootsTests) { using TUnderlying = std::function&, const TString&, TTestWithReboots&)>; diff --git a/ydb/core/tx/schemeshard/ut_export_reboots_s3/ya.make b/ydb/core/tx/schemeshard/ut_export_reboots_s3/ya.make index bc7ca966e0dc..caf4fb7de362 100644 --- a/ydb/core/tx/schemeshard/ut_export_reboots_s3/ya.make +++ b/ydb/core/tx/schemeshard/ut_export_reboots_s3/ya.make @@ -19,6 +19,7 @@ PEERDIR( library/cpp/getopt library/cpp/regex/pcre library/cpp/svnversion + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core ydb/core/testlib/default ydb/core/tx ydb/core/tx/schemeshard/ut_helpers diff --git a/ydb/core/tx/schemeshard/ut_extsubdomain/ut_extsubdomain.cpp b/ydb/core/tx/schemeshard/ut_extsubdomain/ut_extsubdomain.cpp index 4737ccdddc6f..8bbd846af415 100644 --- a/ydb/core/tx/schemeshard/ut_extsubdomain/ut_extsubdomain.cpp +++ b/ydb/core/tx/schemeshard/ut_extsubdomain/ut_extsubdomain.cpp @@ -119,7 +119,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { {NLs::InExternalSubdomain}); } - Y_UNIT_TEST_FLAG(CreateAndAlterWithoutEnablingTx, AlterDatabaseCreateHiveFirst) { + Y_UNIT_TEST_FLAGS(CreateAndAlterWithoutEnablingTx, AlterDatabaseCreateHiveFirst, ExternalHive) { TTestBasicRuntime runtime; TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); ui64 txId = 100; @@ -137,15 +137,19 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { env.TestWaitNotification(runtime, {txId, txId - 1}); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot/dir", - R"( - Name: "USER_0" - ExternalSchemeShard: true - )", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ), {{NKikimrScheme::StatusInvalidParameter, "ExtSubDomain without coordinators/mediators"}} ); } - Y_UNIT_TEST_FLAG(CreateAndAlter, AlterDatabaseCreateHiveFirst) { + Y_UNIT_TEST_FLAGS(CreateAndAlter, AlterDatabaseCreateHiveFirst, ExternalHive) { TTestBasicRuntime runtime; TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); ui64 txId = 100; @@ -156,41 +160,49 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { ); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - Name: "USER_0" - PlanResolution: 50 - Coordinators: 3 - Mediators: 3 - TimeCastBucketsPerMediator: 2 - )", + Sprintf(R"( + Name: "USER_0" + PlanResolution: 50 + Coordinators: 3 + Mediators: 3 + TimeCastBucketsPerMediator: 2 + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ), {{NKikimrScheme::StatusInvalidParameter, "ExtSubDomain without ExternalSchemeShard"}} ); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - StoragePools { - Name: "pool-1" - Kind: "pool-kind-1" - } - StoragePools { - Name: "pool-2" - Kind: "pool-kind-2" - } - StoragePools { - Name: "/dc-1/users/tenant-1:hdd" - Kind: "hdd" - } - StoragePools { - Name: "/dc-1/users/tenant-1:hdd-1" - Kind: "hdd-1" - } - PlanResolution: 50 - Coordinators: 1 - Mediators: 1 - TimeCastBucketsPerMediator: 2 - ExternalSchemeShard: true - Name: "USER_0" - )" + Sprintf(R"( + StoragePools { + Name: "pool-1" + Kind: "pool-kind-1" + } + StoragePools { + Name: "pool-2" + Kind: "pool-kind-2" + } + StoragePools { + Name: "/dc-1/users/tenant-1:hdd" + Kind: "hdd" + } + StoragePools { + Name: "/dc-1/users/tenant-1:hdd-1" + Kind: "hdd-1" + } + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + ExternalSchemeShard: true + Name: "USER_0" + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) ); env.TestWaitNotification(runtime, {txId, txId - 1, txId - 2}); @@ -247,7 +259,64 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { NLs::Finished}); } - Y_UNIT_TEST_FLAG(CreateAndAlterTwice, AlterDatabaseCreateHiveFirst) { + Y_UNIT_TEST_FLAGS(CreateAndSameAlterTwice, AlterDatabaseCreateHiveFirst, ExternalHive) { + TTestBasicRuntime runtime; + TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); + ui64 txId = 100; + + + TestCreateExtSubDomain(runtime, ++txId, "/MyRoot", + R"(Name: "USER_0")" + ); + + const TString alterText = Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ); + + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", alterText); + env.TestWaitNotification(runtime, {txId, txId - 1}); + + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", alterText); + env.TestWaitNotification(runtime, txId); + + ui64 tenantSchemeShard = 0; + TestDescribeResult(DescribePath(runtime, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::IsExternalSubDomain("USER_0"), + NLs::ExtractTenantSchemeshard(&tenantSchemeShard), + }); + + UNIT_ASSERT(tenantSchemeShard != 0 + && tenantSchemeShard != (ui64)-1 + && tenantSchemeShard != TTestTxConfig::SchemeShard + ); + + TestDescribeResult(DescribePath(runtime, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::IsExternalSubDomain("USER_0"), + NLs::StoragePoolsEqual({"pool-1"}), + }); + + TestDescribeResult(DescribePath(runtime, tenantSchemeShard, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::StoragePoolsEqual({"pool-1"}) + }); + } + + Y_UNIT_TEST_FLAGS(CreateAndAlterAlterAddStoragePool, AlterDatabaseCreateHiveFirst, ExternalHive) { TTestBasicRuntime runtime; TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); ui64 txId = 100; @@ -258,34 +327,42 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { ); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - Name: "USER_0" - ExternalSchemeShard: true - PlanResolution: 50 - Coordinators: 1 - Mediators: 1 - TimeCastBucketsPerMediator: 2 - StoragePools { - Name: "pool-1" - Kind: "hdd" - } - )" + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) ); env.TestWaitNotification(runtime, {txId, txId - 1}); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - Name: "USER_0" - StoragePools { - Name: "pool-1" - Kind: "hdd" - } - StoragePools { - Name: "pool-2" - Kind: "hdd-1" - } - )" + Sprintf(R"( + Name: "USER_0" + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + StoragePools { + Name: "pool-2" + Kind: "hdd-1" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) ); env.TestWaitNotification(runtime, txId); @@ -322,6 +399,214 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { NLs::UserAttrsEqual({{"user__attr_1", "value"}})}); } + Y_UNIT_TEST_FLAGS(CreateAndAlterAlterSameStoragePools, AlterDatabaseCreateHiveFirst, ExternalHive) { + TTestBasicRuntime runtime; + TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); + ui64 txId = 100; + + + TestCreateExtSubDomain(runtime, ++txId, "/MyRoot", + R"(Name: "USER_0")" + ); + + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + + env.TestWaitNotification(runtime, {txId, txId - 1}); + + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + DatabaseQuotas { + data_size_hard_quota: 1288490188800 + data_size_soft_quota: 1224065679360 + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + env.TestWaitNotification(runtime, txId); + + + ui64 tenantSchemeShard = 0; + TestDescribeResult(DescribePath(runtime, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::IsExternalSubDomain("USER_0"), + NLs::ExtractTenantSchemeshard(&tenantSchemeShard) + }); + + UNIT_ASSERT(tenantSchemeShard != 0 + && tenantSchemeShard != (ui64)-1 + && tenantSchemeShard != TTestTxConfig::SchemeShard + ); + + TestDescribeResult(DescribePath(runtime, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::IsExternalSubDomain("USER_0"), + NLs::StoragePoolsEqual({"pool-1"}) + }); + + TestDescribeResult(DescribePath(runtime, tenantSchemeShard, "/MyRoot/USER_0"), { + NLs::PathExist, + NLs::StoragePoolsEqual({"pool-1"}) + }); + } + + Y_UNIT_TEST_FLAGS(AlterWithPlainAlterSubdomain, AlterDatabaseCreateHiveFirst, ExternalHive) { + TTestBasicRuntime runtime; + TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); + ui64 txId = 100; + + // Create extsubdomain + + TestCreateExtSubDomain(runtime, ++txId, "/MyRoot", + R"(Name: "USER_0")" + ); + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + env.TestWaitNotification(runtime, {txId, txId - 1}); + + // Altering extsubdomain but with plain altersubdomain should succeed + // (post tenant migration compatibility) + + //NOTE: SubDomain and not ExtSubdomain + TestAlterSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + env.TestWaitNotification(runtime, txId); + } + + Y_UNIT_TEST_FLAGS(AlterTwiceAndWithPlainAlterSubdomain, AlterDatabaseCreateHiveFirst, ExternalHive) { + TTestBasicRuntime runtime; + TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); + ui64 txId = 100; + + TestCreateExtSubDomain(runtime, ++txId, "/MyRoot", + R"(Name: "USER_0")" + ); + TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + env.TestWaitNotification(runtime, {txId, txId - 1}); + + AsyncAlterExtSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) + ); + // TestModificationResults(runtime, txId, {NKikimrScheme::StatusAccepted}); + const auto firstAlterTxId = txId; + + //NOTE: SubDomain vs ExtSubDomain + TestAlterSubDomain(runtime, ++txId, "/MyRoot", + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ), + {{NKikimrScheme::StatusMultipleModifications}} + ); + + env.TestWaitNotification(runtime, firstAlterTxId); + } + Y_UNIT_TEST(CreateWithOnlyDotsNotAllowed) { TTestBasicRuntime runtime; TTestEnv env(runtime); @@ -1082,7 +1367,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { testCreations(); } - Y_UNIT_TEST_FLAG(Drop, AlterDatabaseCreateHiveFirst) { + Y_UNIT_TEST_FLAGS(Drop, AlterDatabaseCreateHiveFirst, ExternalHive) { TTestBasicRuntime runtime; TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); ui64 txId = 100; @@ -1093,18 +1378,22 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { ); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - Name: "USER_0" - ExternalSchemeShard: true - PlanResolution: 50 - Coordinators: 1 - Mediators: 1 - TimeCastBucketsPerMediator: 2 - StoragePools { - Name: "pool-1" - Kind: "hdd" - } - )" + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) ); env.TestWaitNotification(runtime, {txId, txId - 1}); @@ -1139,16 +1428,18 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { NLs::PathsInsideDomain(1), NLs::ShardsInsideDomain(0)}); + const ui64 AdditionalHiveTablet = (ExternalHive ? 1 : 0); + TestDescribeResult(DescribePath(runtime, "/MyRoot/USER_0/dir/table_1"), {NLs::PathRedirected, NLs::PathsInsideDomain(0), - NLs::ShardsInsideDomain(3)}); + NLs::ShardsInsideDomain(3 + AdditionalHiveTablet)}); TestDescribeResult(DescribePath(runtime, tenantSchemeShard, "/MyRoot/USER_0/dir/table_1"), {NLs::PathExist, NLs::Finished, NLs::PathsInsideDomain(2), - NLs::ShardsInsideDomain(5)}); + NLs::ShardsInsideDomain(5 + AdditionalHiveTablet)}); TestForceDropExtSubDomain(runtime, ++txId, "/MyRoot", "USER_0"); env.TestWaitNotification(runtime, txId); @@ -1164,7 +1455,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { NLs::PathsInsideDomain(0), NLs::ShardsInsideDomain(0)}); - env.TestWaitTabletDeletion(runtime, xrange(TTestTxConfig::FakeHiveTablets, TTestTxConfig::FakeHiveTablets + 5)); + // env.TestWaitTabletDeletion(runtime, xrange(TTestTxConfig::FakeHiveTablets, TTestTxConfig::FakeHiveTablets + 5)); UNIT_ASSERT(!CheckLocalRowExists(runtime, TTestTxConfig::SchemeShard, "SubDomains", "PathId", 2)); UNIT_ASSERT(!CheckLocalRowExists(runtime, TTestTxConfig::SchemeShard, "Paths", "Id", 2)); } @@ -1209,7 +1500,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { } } - Y_UNIT_TEST_FLAG(CreateAndAlterThenDropChangesParent, AlterDatabaseCreateHiveFirst) { + Y_UNIT_TEST_FLAGS(CreateAndAlterThenDropChangesParent, AlterDatabaseCreateHiveFirst, ExternalHive) { TTestBasicRuntime runtime; TTestEnv env(runtime, TTestEnvOptions().EnableAlterDatabaseCreateHiveFirst(AlterDatabaseCreateHiveFirst)); ui64 txId = 100; @@ -1218,18 +1509,22 @@ Y_UNIT_TEST_SUITE(TSchemeShardExtSubDomainTest) { R"(Name: "USER_0")" ); TestAlterExtSubDomain(runtime, ++txId, "/MyRoot", - R"( - Name: "USER_0" - ExternalSchemeShard: true - PlanResolution: 50 - Coordinators: 1 - Mediators: 1 - TimeCastBucketsPerMediator: 2 - StoragePools { - Name: "pool-1" - Kind: "hdd" - } - )" + Sprintf(R"( + Name: "USER_0" + ExternalSchemeShard: true + PlanResolution: 50 + Coordinators: 1 + Mediators: 1 + TimeCastBucketsPerMediator: 2 + StoragePools { + Name: "pool-1" + Kind: "hdd" + } + + ExternalHive: %s + )", + ToString(ExternalHive).c_str() + ) ); env.TestWaitNotification(runtime, {txId, txId - 1}); diff --git a/ydb/core/tx/schemeshard/ut_helpers/helpers.h b/ydb/core/tx/schemeshard/ut_helpers/helpers.h index 94222eaaaa10..03f2af2d8fb5 100644 --- a/ydb/core/tx/schemeshard/ut_helpers/helpers.h +++ b/ydb/core/tx/schemeshard/ut_helpers/helpers.h @@ -48,6 +48,19 @@ template \ void N(NUnitTest::TTestContext&) +#define Y_UNIT_TEST_FLAGS(N, OPT1, OPT2) \ + template void N(NUnitTest::TTestContext&); \ + struct TTestRegistration##N { \ + TTestRegistration##N() { \ + TCurrentTest::AddTest(#N, static_cast(&N), false); \ + TCurrentTest::AddTest(#N "-" #OPT2, static_cast(&N), false); \ + TCurrentTest::AddTest(#N "-" #OPT1, static_cast(&N), false); \ + TCurrentTest::AddTest(#N "-" #OPT1 "-" #OPT2, static_cast(&N), false); \ + } \ + }; \ + static TTestRegistration##N testRegistration##N; \ + template \ + void N(NUnitTest::TTestContext&) namespace NSchemeShardUT_Private { using namespace NKikimr; diff --git a/ydb/core/tx/schemeshard/ut_index_build/ut_index_build.cpp b/ydb/core/tx/schemeshard/ut_index_build/ut_index_build.cpp index 4ab86bcf9a7c..59b72b11c75d 100644 --- a/ydb/core/tx/schemeshard/ut_index_build/ut_index_build.cpp +++ b/ydb/core/tx/schemeshard/ut_index_build/ut_index_build.cpp @@ -667,6 +667,36 @@ Y_UNIT_TEST_SUITE(IndexBuildTest) { env.TestWaitNotification(runtime, {txId, txId - 1}); } + Y_UNIT_TEST(CheckLimitWithDroppedIndex) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + TSchemeLimits lowLimits; + lowLimits.MaxTableIndices = 1; + SetSchemeshardSchemaLimits(runtime, lowLimits); + + TestCreateTable(runtime, ++txId, "/MyRoot", R"( + Name: "Table" + Columns { Name: "key" Type: "Uint64" } + Columns { Name: "value" Type: "Utf8" } + KeyColumnNames: ["key"] + )"); + env.TestWaitNotification(runtime, txId); + + TestBuildIndex(runtime, ++txId, TTestTxConfig::SchemeShard, "/MyRoot", "/MyRoot/Table", "Index1", {"value"}, Ydb::StatusIds::SUCCESS); + env.TestWaitNotification(runtime, txId); + + TestDropTableIndex(runtime, ++txId, "/MyRoot", R"( + TableName: "Table" + IndexName: "Index1" + )"); + env.TestWaitNotification(runtime, txId); + + TestBuildIndex(runtime, ++txId, TTestTxConfig::SchemeShard, "/MyRoot", "/MyRoot/Table", "Index2", {"value"}, Ydb::StatusIds::SUCCESS); + env.TestWaitNotification(runtime, txId); + } + Y_UNIT_TEST(Lock) { TTestBasicRuntime runtime; TTestEnv env(runtime); diff --git a/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp b/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp index cee6f82143e7..6007cf9619d5 100644 --- a/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp +++ b/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp @@ -21,8 +21,10 @@ #include +#include #include #include +#include #include #include @@ -38,6 +40,16 @@ using namespace NKikimr::NWrappers::NTestHelpers; namespace { + Aws::SDKOptions Options; + + Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); + } + + Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); + } + const TString EmptyYsonStr = R"([[[[];%false]]])"; TString GenerateScheme(const NKikimrSchemeOp::TPathDescription& pathDesc) { @@ -317,7 +329,6 @@ namespace { runtime.SetObserverFunc(prevObserver); } - } // anonymous Y_UNIT_TEST_SUITE(TRestoreTests) { diff --git a/ydb/core/tx/schemeshard/ut_restore/ya.make b/ydb/core/tx/schemeshard/ut_restore/ya.make index 7044d4283b5e..d514b36b49ee 100644 --- a/ydb/core/tx/schemeshard/ut_restore/ya.make +++ b/ydb/core/tx/schemeshard/ut_restore/ya.make @@ -14,6 +14,7 @@ ELSE() ENDIF() PEERDIR( + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core contrib/libs/double-conversion library/cpp/string_utils/quote ydb/core/kqp/ut/common diff --git a/ydb/core/viewer/browse.h b/ydb/core/viewer/browse.h index aa29d6c0f3a4..69569a35b61c 100644 --- a/ydb/core/viewer/browse.h +++ b/ydb/core/viewer/browse.h @@ -1,21 +1,16 @@ #pragma once -#include -#include -#include +#include "browse_events.h" +#include "viewer.h" +#include "wb_aggregate.h" #include #include #include -#include +#include #include #include -#include -#include -#include "browse_events.h" -#include "viewer.h" -#include "wb_aggregate.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -646,4 +641,3 @@ class TBrowseTabletsCommon : public TActorBootstrapped { }; } -} diff --git a/ydb/core/viewer/browse_db.h b/ydb/core/viewer/browse_db.h index 86c4aa264127..f794cb3bbab4 100644 --- a/ydb/core/viewer/browse_db.h +++ b/ydb/core/viewer/browse_db.h @@ -1,20 +1,12 @@ #pragma once -#include -#include +#include "browse.h" +#include "viewer.h" +#include "wb_aggregate.h" #include -#include -#include #include #include -#include -#include -#include -#include "viewer.h" -#include "browse.h" -#include "wb_aggregate.h" -namespace NKikimr { -namespace NViewerDB { +namespace NKikimr::NViewerDB { using namespace NViewer; using namespace NActors; @@ -200,4 +192,3 @@ class TBrowseTable : public TBrowseTabletsCommon { }; } -} diff --git a/ydb/core/viewer/browse_events.h b/ydb/core/viewer/browse_events.h index ee0bbbb37572..92cc2b206bec 100644 --- a/ydb/core/viewer/browse_events.h +++ b/ydb/core/viewer/browse_events.h @@ -1,13 +1,12 @@ #pragma once -#include -#include -#include - #include +#include +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { namespace NViewerEvents { enum EEv { @@ -76,5 +75,4 @@ namespace NViewerEvents { }; } // namespace NViewerEvents -} // namespace NViewer -} // namespace NKikimr +} diff --git a/ydb/core/viewer/browse_pq.h b/ydb/core/viewer/browse_pq.h index 1f2b5d950b16..190b72ad953b 100644 --- a/ydb/core/viewer/browse_pq.h +++ b/ydb/core/viewer/browse_pq.h @@ -1,20 +1,12 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "browse.h" +#include "viewer.h" #include "wb_aggregate.h" +#include +#include +#include -namespace NKikimr { -namespace NViewerPQ { +namespace NKikimr::NViewerPQ { using namespace NViewer; using namespace NActors; @@ -419,4 +411,3 @@ class TBrowseTopic : public TBrowseCommon { }; } -} diff --git a/ydb/core/viewer/check_access.h b/ydb/core/viewer/check_access.h deleted file mode 100644 index 42526b2bffe1..000000000000 --- a/ydb/core/viewer/check_access.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "json_pipe_req.h" - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; -using NSchemeShard::TEvSchemeShard; -using TNavigate = NSchemeCache::TSchemeCacheNavigate; - -class TCheckAccess : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TAutoPtr CacheResult; - TVector Permissions; - -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TCheckAccess(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) - {} - - void Bootstrap() { - const auto& params(Event->Get()->Request.GetParams()); - ui32 timeout = FromStringWithDefault(params.Get("timeout"), 10000); - TString database; - if (params.Has("database")) { - database = params.Get("database"); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - if (database && database != AppData()->TenantName) { - BLOG_TRACE("Requesting StateStorageEndpointsLookup for " << database); - RequestStateStorageEndpointsLookup(database); // to find some dynamic node and redirect query there - } else { - if (params.Has("permissions")) { - Split(params.Get("permissions"), ",", Permissions); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'permissions' is required")); - } - if (params.Has("path")) { - RequestSchemeCacheNavigate(params.Get("path")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'path' is required")); - } - } - Become(&TThis::StateRequestedNavigate, TDuration::MilliSeconds(timeout), new TEvents::TEvWakeup()); - } - - void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); - ReplyAndPassAway(Viewer->MakeForward(Event->Get(), GetNodesFromBoardReply(ev))); - } - - STATEFN(StateRequestedNavigate) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvStateStorage::TEvBoardInfo, Handle); - hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { - CacheResult = ev->Release(); - RequestDone(); - } - - ui32 GetAccessType(const TString& permission) { - TACLAttrs attrs(0); - try { - attrs = ConvertYdbPermissionNameToACLAttrs(permission); - } - catch (const std::exception&) { - } - return attrs.AccessMask; - } - - bool CheckAccessPermission(const NACLib::TSecurityObject* object, const NACLib::TUserToken* token, const TString& permission) { - const auto& kikimrRunConfig = Viewer->GetKikimrRunConfig(); - const auto& securityConfig = kikimrRunConfig.AppConfig.GetDomainsConfig().GetSecurityConfig(); - if (!securityConfig.GetEnforceUserTokenRequirement()) { - if (!securityConfig.GetEnforceUserTokenCheckRequirement() || token == nullptr) { - return true; - } - } - if (token == nullptr) { - return false; - } - if (object == nullptr) { - return false; - } - ui32 access = GetAccessType(permission); - if (access == 0) { - return false; - } - return object->CheckAccess(access, *token); - } - - void ReplyAndPassAway() { - std::unique_ptr token; - if (Event->Get()->UserToken) { - token = std::make_unique(Event->Get()->UserToken); - } - if (CacheResult == nullptr) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "no SchemeCache response")); - } - if (CacheResult->Request == nullptr) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "wrong SchemeCache response")); - } - if (CacheResult->Request.Get()->ResultSet.empty()) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "SchemeCache response is empty")); - } - if (CacheResult->Request.Get()->ErrorCount != 0) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", TStringBuilder() << "SchemeCache response error " << static_cast(CacheResult->Request.Get()->ResultSet.front().Status))); - } - - - auto object = CacheResult->Request.Get()->ResultSet.front().SecurityObject; - - NJson::TJsonValue json(NJson::JSON_MAP); - - for (const TString& permission : Permissions) { - json[permission] = CheckAccessPermission(object.Get(), token.get(), permission); - } - - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false))); - } - - void HandleTimeout() { - ReplyAndPassAway(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get(), "text/plain", "Timeout receiving SchemeCache response")); - } - - void ReplyAndPassAway(TString data) { - Send(Event->Sender, new NMon::TEvHttpInfoRes(data, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } -}; - -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - viewer - summary: Check access - description: Check access to the specified path - parameters: - - name: database - in: query - description: database name - type: string - required: true - - name: path - in: query - description: path to check access - type: string - required: true - - name: permissions - in: query - description: permissions to check - required: true - type: array - items: - type: string - enum: - - ydb.database.connect - - ydb.tables.modify - - ydb.tables.read - - ydb.generic.list - - ydb.generic.read - - ydb.generic.write - - ydb.generic.use_legacy - - ydb.generic.use - - ydb.generic.manage - - ydb.generic.full_legacy - - ydb.generic.full - - ydb.database.create - - ydb.database.drop - - ydb.access.grant - - ydb.granular.select_row - - ydb.granular.update_row - - ydb.granular.erase_row - - ydb.granular.read_attributes - - ydb.granular.write_attributes - - ydb.granular.create_directory - - ydb.granular.create_table - - ydb.granular.create_queue - - ydb.granular.remove_schema - - ydb.granular.describe_schema - - ydb.granular.alter_schema - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - - return node; -} - -} -} - diff --git a/ydb/core/viewer/counters_hosts.h b/ydb/core/viewer/counters_hosts.h index 2232f16b00ba..b93a7cb0b485 100644 --- a/ydb/core/viewer/counters_hosts.h +++ b/ydb/core/viewer/counters_hosts.h @@ -1,16 +1,12 @@ #pragma once -#include -#include -#include -#include +#include "viewer.h" #include #include -#include #include -#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; @@ -173,4 +169,3 @@ class TCountersHostsList : public TActorBootstrapped { }; } -} diff --git a/ydb/core/viewer/healthcheck_record.h b/ydb/core/viewer/healthcheck_record.h index 39c94e288773..5b55d2bd1322 100644 --- a/ydb/core/viewer/healthcheck_record.h +++ b/ydb/core/viewer/healthcheck_record.h @@ -1,9 +1,8 @@ #pragma once +#include +#include namespace NKikimr::NViewer { - -using namespace NActors; -using namespace NMonitoring; struct TMetricRecord { TString Database; diff --git a/ydb/core/viewer/json_acl.h b/ydb/core/viewer/json_acl.h deleted file mode 100644 index d484ac4f2860..000000000000 --- a/ydb/core/viewer/json_acl.h +++ /dev/null @@ -1,303 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "viewer.h" -#include "json_pipe_req.h" - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; -using NSchemeShard::TEvSchemeShard; - -class TJsonACL : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TAutoPtr CacheResult; - TJsonSettings JsonSettings; - bool MergeRules = false; - ui32 Timeout = 0; - -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonACL(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) - {} - - void Bootstrap() { - const auto& params(Event->Get()->Request.GetParams()); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - TString database; - if (params.Has("database")) { - database = params.Get("database"); - } - if (database && database != AppData()->TenantName) { - BLOG_TRACE("Requesting StateStorageEndpointsLookup for " << database); - RequestStateStorageEndpointsLookup(database); // to find some dynamic node and redirect query there - } else { - if (params.Has("path")) { - RequestSchemeCacheNavigate(params.Get("path")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'path' is required")); - } - MergeRules = FromStringWithDefault(params.Get("merge_rules"), MergeRules); - } - - Become(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } - - void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); - ReplyAndPassAway(Viewer->MakeForward(Event->Get(), GetNodesFromBoardReply(ev))); - } - - STATEFN(StateRequestedDescribe) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvStateStorage::TEvBoardInfo, Handle); - hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { - CacheResult = ev->Release(); - RequestDone(); - } - - static bool Has(ui32 accessRights, ui32 mask) { - return (accessRights & mask) == mask; - } - - void FillACE(const NACLibProto::TACE& ace, NKikimrViewer::TMetaCommonInfo::TACE& pbAce) { - if (static_cast(ace.GetAccessType()) == NACLib::EAccessType::Deny) { - pbAce.SetAccessType("Deny"); - } - if (static_cast(ace.GetAccessType()) == NACLib::EAccessType::Allow) { - pbAce.SetAccessType("Allow"); - } - - auto ar = ace.GetAccessRight(); - - static std::pair accessRules[] = { - {NACLib::EAccessRights::GenericFull, "Full"}, - {NACLib::EAccessRights::GenericFullLegacy, "FullLegacy"}, - {NACLib::EAccessRights::GenericManage, "Manage"}, - {NACLib::EAccessRights::GenericUse, "Use"}, - {NACLib::EAccessRights::GenericUseLegacy, "UseLegacy"}, - {NACLib::EAccessRights::GenericWrite, "Write"}, - {NACLib::EAccessRights::GenericRead, "Read"}, - {NACLib::EAccessRights::GenericList, "List"}, - }; - if (MergeRules) { - for (const auto& [rule, name] : accessRules) { - if (Has(ar, rule)) { - pbAce.AddAccessRules(name); - ar &= ~rule; - } - } - } - - static std::pair accessRights[] = { - {NACLib::EAccessRights::SelectRow, "SelectRow"}, - {NACLib::EAccessRights::UpdateRow, "UpdateRow"}, - {NACLib::EAccessRights::EraseRow, "EraseRow"}, - {NACLib::EAccessRights::ReadAttributes, "ReadAttributes"}, - {NACLib::EAccessRights::WriteAttributes, "WriteAttributes"}, - {NACLib::EAccessRights::CreateDirectory, "CreateDirectory"}, - {NACLib::EAccessRights::CreateTable, "CreateTable"}, - {NACLib::EAccessRights::CreateQueue, "CreateQueue"}, - {NACLib::EAccessRights::RemoveSchema, "RemoveSchema"}, - {NACLib::EAccessRights::DescribeSchema, "DescribeSchema"}, - {NACLib::EAccessRights::AlterSchema, "AlterSchema"}, - {NACLib::EAccessRights::CreateDatabase, "CreateDatabase"}, - {NACLib::EAccessRights::DropDatabase, "DropDatabase"}, - {NACLib::EAccessRights::GrantAccessRights, "GrantAccessRights"}, - {NACLib::EAccessRights::WriteUserAttributes, "WriteUserAttributes"}, - {NACLib::EAccessRights::ConnectDatabase, "ConnectDatabase"}, - {NACLib::EAccessRights::ReadStream, "ReadStream"}, - {NACLib::EAccessRights::WriteStream, "WriteStream"}, - {NACLib::EAccessRights::ReadTopic, "ReadTopic"}, - {NACLib::EAccessRights::WriteTopic, "WriteTopic"} - }; - for (const auto& [right, name] : accessRights) { - if (Has(ar, right)) { - pbAce.AddAccessRights(name); - ar &= ~right; - } - } - - if (ar != 0) { - pbAce.AddAccessRights(NACLib::AccessRightsToString(ar)); - } - - pbAce.SetSubject(ace.GetSID()); - - auto inht = ace.GetInheritanceType(); - if ((inht & NACLib::EInheritanceType::InheritObject) != 0) { - pbAce.AddInheritanceType("Object"); - } - if ((inht & NACLib::EInheritanceType::InheritContainer) != 0) { - pbAce.AddInheritanceType("Container"); - } - if ((inht & NACLib::EInheritanceType::InheritOnly) != 0) { - pbAce.AddInheritanceType("Only"); - } - } - - void ReplyAndPassAway(TString data) { - Send(Event->Sender, new NMon::TEvHttpInfoRes(data, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } - - void ReplyAndPassAway() { - if (CacheResult == nullptr) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "no SchemeCache response")); - } - if (CacheResult->Request == nullptr) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "wrong SchemeCache response")); - } - if (CacheResult->Request.Get()->ResultSet.empty()) { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get(), "text/plain", "SchemeCache response is empty")); - } - if (CacheResult->Request.Get()->ErrorCount != 0) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", TStringBuilder() << "SchemeCache response error " << static_cast(CacheResult->Request.Get()->ResultSet.front().Status))); - } - const auto& entry = CacheResult->Request.Get()->ResultSet.front(); - NKikimrViewer::TMetaInfo metaInfo; - NKikimrViewer::TMetaCommonInfo& pbCommon = *metaInfo.MutableCommon(); - pbCommon.SetPath(CanonizePath(entry.Path)); - pbCommon.SetOwner(entry.Self->Info.GetOwner()); - if (entry.Self->Info.HasACL()) { - NACLib::TACL acl(entry.Self->Info.GetACL()); - for (const NACLibProto::TACE& ace : acl.GetACE()) { - auto& pbAce = *pbCommon.AddACL(); - FillACE(ace, pbAce); - } - } - if (entry.Self->Info.HasEffectiveACL()) { - NACLib::TACL acl(entry.Self->Info.GetEffectiveACL()); - for (const NACLibProto::TACE& ace : acl.GetACE()) { - auto& pbAce = *pbCommon.AddEffectiveACL(); - FillACE(ace, pbAce); - } - } - - TStringStream json; - TProtoToJson::ProtoToJson(json, metaInfo, JsonSettings); - - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), json.Str())); - } - - void HandleTimeout() { - ReplyAndPassAway(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get())); - } -}; - -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - viewer - summary: ACL information - description: Returns information about ACL of an object - parameters: - - name: database - in: query - description: database name - type: string - required: false - - name: path - in: query - description: schema path - required: true - type: string - - name: merge_rules - in: query - description: merge access rights into access rules - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - properties: - Common: - type: object - properties: - Path: - type: string - Owner: - type: string - ACL: - type: array - items: - type: object - properties: - AccessType: - type: string - Subject: - type: string - AccessRules: - type: array - items: - type: string - AccessRights: - type: array - items: - type: string - InheritanceType: - type: array - items: - type: string - EffectiveACL: - type: array - items: - type: object - properties: - AccessType: - type: string - Subject: - type: string - AccessRules: - type: array - items: - type: string - AccessRights: - type: array - items: - type: string - InheritanceType: - type: array - items: - type: string - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - - )___"); - - return node; -} - -} -} diff --git a/ydb/core/viewer/json_autocomplete.h b/ydb/core/viewer/json_autocomplete.h deleted file mode 100644 index 155a960af2ea..000000000000 --- a/ydb/core/viewer/json_autocomplete.h +++ /dev/null @@ -1,519 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -#include "query_autocomplete_helper.h" -#include "viewer_request.h" - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; -using TNavigate = NSchemeCache::TSchemeCacheNavigate; - -class TJsonAutocomplete : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TEvViewer::TEvViewerRequest::TPtr ViewerRequest; - TJsonSettings JsonSettings; - ui32 Timeout = 0; - - TAutoPtr ProxyResult; - TAutoPtr ConsoleResult; - TAutoPtr CacheResult; - - struct TSchemaWordData { - TString Name; - NKikimrViewer::EAutocompleteType Type; - TString Table; - TSchemaWordData() {} - TSchemaWordData(const TString& name, const NKikimrViewer::EAutocompleteType type, const TString& table = "") - : Name(name) - , Type(type) - , Table(table) - {} - }; - THashMap Dictionary; - TString Database; - TVector Tables; - TVector Paths; - TString Prefix; - TString SearchWord; - ui32 Limit = 10; - NKikimrViewer::TQueryAutocomplete Result; - - std::optional SubscribedNodeId; - std::vector TenantDynamicNodes; - bool Direct = false; -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonAutocomplete(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) - { - const auto& params(Event->Get()->Request.GetParams()); - InitConfig(params); - ParseCgiParameters(params); - if (IsPostContent()) { - TStringBuf content = Event->Get()->Request.GetPostContent(); - ParsePostContent(content); - } - PrepareParameters(); - } - - // proxied request - TJsonAutocomplete(TEvViewer::TEvViewerRequest::TPtr& ev) - : ViewerRequest(ev) - { - auto& request = ViewerRequest->Get()->Record.GetAutocompleteRequest(); - - Database = request.GetDatabase(); - for (auto& table: request.GetTables()) { - Tables.emplace_back(table); - } - Prefix = request.GetPrefix(); - Limit = request.GetLimit(); - - Timeout = ViewerRequest->Get()->Record.GetTimeout(); - Direct = true; - PrepareParameters(); - } - - void PrepareParameters() { - if (Database) { - TString prefixUpToLastSlash = ""; - auto splitPos = Prefix.find_last_of('/'); - if (splitPos != std::string::npos) { - prefixUpToLastSlash += Prefix.substr(0, splitPos); - SearchWord = Prefix.substr(splitPos + 1); - } else { - SearchWord = Prefix; - } - - if (Tables.size() == 0) { - Paths.emplace_back(Database); - } else { - for (TString& table: Tables) { - TString path = table; - if (!table.StartsWith(Database)) { - path = Database + "/" + path; - } - path += "/" + prefixUpToLastSlash; - Paths.emplace_back(path); - } - } - } else { - SearchWord = Prefix; - } - if (Limit == 0) { - Limit = std::numeric_limits::max(); - } - } - - void ParseCgiParameters(const TCgiParameters& params) { - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); - Database = params.Get("database"); - StringSplitter(params.Get("table")).Split(',').SkipEmpty().Collect(&Tables); - Prefix = params.Get("prefix"); - Limit = FromStringWithDefault(params.Get("limit"), Limit); - Direct = FromStringWithDefault(params.Get("direct"), Direct); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - } - - void ParsePostContent(const TStringBuf& content) { - static NJson::TJsonReaderConfig JsonConfig; - NJson::TJsonValue requestData; - bool success = NJson::ReadJsonTree(content, &JsonConfig, &requestData); - if (success) { - Database = Database.empty() ? requestData["database"].GetStringSafe({}) : Database; - if (requestData["table"].IsArray()) { - for (auto& table: requestData["table"].GetArraySafe()) { - Tables.emplace_back(table.GetStringSafe()); - } - } - Prefix = Prefix.empty() ? requestData["prefix"].GetStringSafe({}) : Prefix; - if (requestData["limit"].IsDefined()) { - Limit = requestData["limit"].GetInteger(); - } - } - } - - bool IsPostContent() const { - return NViewer::IsPostContent(Event); - } - - TAutoPtr MakeSchemeCacheRequest() { - TAutoPtr request(new NSchemeCache::TSchemeCacheNavigate()); - - for (TString& path: Paths) { - NSchemeCache::TSchemeCacheNavigate::TEntry entry; - entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpList; - entry.SyncVersion = false; - entry.Path = SplitPath(path); - request->ResultSet.emplace_back(entry); - } - - return request; - } - - void Bootstrap() { - if (ViewerRequest) { - // handle proxied request - SendSchemeCacheRequest(); - } else if (!Database) { - // autocomplete database list via console request - RequestConsoleListTenants(); - } else { - if (!Direct) { - // proxy request to a dynamic node of the specified database - RequestStateStorageEndpointsLookup(Database); - } - if (Requests == 0) { - // perform autocomplete without proxying - SendSchemeCacheRequest(); - } - } - - Become(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } - - void Connected(TEvInterconnect::TEvNodeConnected::TPtr &) {} - - void Undelivered(TEvents::TEvUndelivered::TPtr &ev) { - if (!Direct && ev->Get()->SourceType == NViewer::TEvViewer::EvViewerRequest) { - Direct = true; - SendSchemeCacheRequest(); // fallback - RequestDone(); - } - } - - void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr &) { - if (!Direct) { - Direct = true; - SendSchemeCacheRequest(); // fallback - RequestDone(); - } - } - - void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); - if (ev->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { - for (const auto& [actorId, infoEntry] : ev->Get()->InfoEntries) { - TenantDynamicNodes.emplace_back(actorId.NodeId()); - } - } - if (TenantDynamicNodes.empty()) { - SendSchemeCacheRequest(); - } else { - SendDynamicNodeAutocompleteRequest(); - } - RequestDone(); - } - - void SendSchemeCacheRequest() { - SendRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(MakeSchemeCacheRequest())); - } - - void SendDynamicNodeAutocompleteRequest() { - ui64 hash = std::hash()(Event->Get()->Request.GetRemoteAddr()); - - auto itPos = std::next(TenantDynamicNodes.begin(), hash % TenantDynamicNodes.size()); - std::nth_element(TenantDynamicNodes.begin(), itPos, TenantDynamicNodes.end()); - - TNodeId nodeId = *itPos; - SubscribedNodeId = nodeId; - TActorId viewerServiceId = MakeViewerID(nodeId); - - THolder request = MakeHolder(); - request->Record.SetTimeout(Timeout); - auto autocompleteRequest = request->Record.MutableAutocompleteRequest(); - autocompleteRequest->SetDatabase(Database); - for (TString& path: Paths) { - autocompleteRequest->AddTables(path); - } - autocompleteRequest->SetPrefix(Prefix); - autocompleteRequest->SetLimit(Limit); - - ViewerWhiteboardCookie cookie(NKikimrViewer::TEvViewerRequest::kAutocompleteRequest, nodeId); - SendRequest(viewerServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, cookie.ToUi64()); - } - - void PassAway() override { - if (SubscribedNodeId.has_value()) { - Send(TActivationContext::InterconnectProxy(SubscribedNodeId.value()), new TEvents::TEvUnsubscribe()); - } - TBase::PassAway(); - BLOG_TRACE("PassAway()"); - } - - STATEFN(StateRequestedDescribe) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvStateStorage::TEvBoardInfo, Handle); - hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); - hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); - hFunc(TEvents::TEvUndelivered, Undelivered); - hFunc(TEvInterconnect::TEvNodeConnected, Connected); - hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); - hFunc(TEvViewer::TEvViewerResponse, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - void ParseProxyResult() { - if (ProxyResult == nullptr) { - Result.add_error("Failed to collect information from ProxyResult"); - return; - } - if (ProxyResult->Record.HasAutocompleteResponse()) { - Result = ProxyResult->Record.GetAutocompleteResponse(); - } else { - Result.add_error("Proxying return empty response"); - } - - } - - void ParseConsoleResult() { - if (ConsoleResult == nullptr) { - Result.add_error("Failed to collect information from ConsoleResult"); - return; - } - - Ydb::Cms::ListDatabasesResult listTenantsResult; - ConsoleResult->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); - for (const TString& path : listTenantsResult.paths()) { - Dictionary[path] = TSchemaWordData(path, NKikimrViewer::ext_sub_domain); - } - } - - NKikimrViewer::EAutocompleteType ConvertType(TNavigate::EKind navigate) { - switch (navigate) { - case TNavigate::KindSubdomain: - return NKikimrViewer::sub_domain; - case TNavigate::KindPath: - return NKikimrViewer::dir; - case TNavigate::KindExtSubdomain: - return NKikimrViewer::ext_sub_domain; - case TNavigate::KindTable: - return NKikimrViewer::table; - case TNavigate::KindOlapStore: - return NKikimrViewer::column_store; - case TNavigate::KindColumnTable: - return NKikimrViewer::column_table; - case TNavigate::KindRtmr: - return NKikimrViewer::rtmr_volume; - case TNavigate::KindKesus: - return NKikimrViewer::kesus; - case TNavigate::KindSolomon: - return NKikimrViewer::solomon_volume; - case TNavigate::KindTopic: - return NKikimrViewer::pers_queue_group; - case TNavigate::KindCdcStream: - return NKikimrViewer::cdc_stream; - case TNavigate::KindSequence: - return NKikimrViewer::sequence; - case TNavigate::KindReplication: - return NKikimrViewer::replication; - case TNavigate::KindBlobDepot: - return NKikimrViewer::blob_depot; - case TNavigate::KindExternalTable: - return NKikimrViewer::external_table; - case TNavigate::KindExternalDataSource: - return NKikimrViewer::external_data_source; - case TNavigate::KindBlockStoreVolume: - return NKikimrViewer::block_store_volume; - case TNavigate::KindFileStore: - return NKikimrViewer::file_store; - case TNavigate::KindView: - return NKikimrViewer::view; - default: - return NKikimrViewer::dir; - } - } - - void ParseCacheResult() { - if (CacheResult == nullptr) { - Result.add_error("Failed to collect information from CacheResult"); - return; - } - NSchemeCache::TSchemeCacheNavigate *navigate = CacheResult->Request.Get(); - if (navigate->ErrorCount > 0) { - for (auto& entry: CacheResult->Request.Get()->ResultSet) { - if (entry.Status != TSchemeCacheNavigate::EStatus::Ok) { - Result.add_error(TStringBuilder() << "Error receiving Navigate response: `" << CanonizePath(entry.Path) << "` has <" << ToString(entry.Status) << "> status"); - } - } - return; - } - for (auto& entry: CacheResult->Request.Get()->ResultSet) { - TString path = CanonizePath(entry.Path); - if (entry.ListNodeEntry) { - for (const auto& child : entry.ListNodeEntry->Children) { - Dictionary[child.Name] = TSchemaWordData(child.Name, ConvertType(child.Kind), path); - } - }; - for (const auto& [id, column] : entry.Columns) { - Dictionary[column.Name] = TSchemaWordData(column.Name, NKikimrViewer::column, path); - } - for (const auto& index : entry.Indexes) { - Dictionary[index.GetName()] = TSchemaWordData(index.GetName(), NKikimrViewer::index, path); - } - for (const auto& cdcStream : entry.CdcStreams) { - Dictionary[cdcStream.GetName()] = TSchemaWordData(cdcStream.GetName(), NKikimrViewer::cdc_stream, path); - } - } - } - - void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr &ev) { - CacheResult = ev->Release(); - RequestDone(); - } - - void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { - ConsoleResult = ev->Release(); - RequestDone(); - } - - void SendAutocompleteResponse() { - if (ViewerRequest) { - TEvViewer::TEvViewerResponse* viewerResponse = new TEvViewer::TEvViewerResponse(); - viewerResponse->Record.MutableAutocompleteResponse()->CopyFrom(Result); - Send(ViewerRequest->Sender, viewerResponse); - } else { - TStringStream json; - TProtoToJson::ProtoToJson(json, Result, JsonSettings); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - } - } - - void ReplyAndPassAway() { - if (ProxyResult) { - ParseProxyResult(); - } else if (Database) { - ParseCacheResult(); - } else { - ParseConsoleResult(); - } - - if (!ProxyResult) { - Result.set_success(Result.error_size() == 0); - if (Result.error_size() == 0) { - auto fuzzy = FuzzySearcher(Dictionary); - auto autocomplete = fuzzy.Search(SearchWord, Limit); - Result.MutableResult()->SetTotal(autocomplete.size()); - for (TSchemaWordData& wordData: autocomplete) { - auto entity = Result.MutableResult()->AddEntities(); - entity->SetName(wordData.Name); - entity->SetType(wordData.Type); - if (wordData.Table) { - entity->SetParent(wordData.Table); - } - } - } - } - - SendAutocompleteResponse(); - PassAway(); - } - - void Handle(TEvViewer::TEvViewerResponse::TPtr& ev) { - if (ev.Get()->Get()->Record.HasAutocompleteResponse()) { - ProxyResult = ev.Release()->Release(); - } else { - Direct = true; - SendSchemeCacheRequest(); // fallback - } - RequestDone(); - } - - void HandleTimeout() { - if (ViewerRequest) { - Result.add_error("Request timed out"); - ReplyAndPassAway(); - } else { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } - - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: database - in: query - description: database name - required: false - type: string - - name: table - in: query - description: table list - required: false - type: string - - name: prefix - in: query - description: known part of the word - required: false - type: string - - name: limit - in: query - description: limit of entities - required: false - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: direct - in: query - description: force execution on current node - required: false - type: boolean - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Autocomplete information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns autocomplete information about objects in the database"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_blobindexstat.h b/ydb/core/viewer/json_blobindexstat.h deleted file mode 100644 index 2cdf56b4bc4f..000000000000 --- a/ydb/core/viewer/json_blobindexstat.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "json_vdisk_req.h" - -namespace NKikimr { -namespace NViewer { - -using TJsonBlobIndexStat = TJsonVDiskRequest; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "\"Get logoblob index stat from VDisk\""; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "\"Get logoblob index stat from VDisk\""; - } -}; - -} -} diff --git a/ydb/core/viewer/json_bscontrollerinfo.h b/ydb/core/viewer/json_bscontrollerinfo.h deleted file mode 100644 index 1e0263115a5f..000000000000 --- a/ydb/core/viewer/json_bscontrollerinfo.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include "viewer.h" -#include "json_pipe_req.h" -#include - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; - -class TJsonBSControllerInfo : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TAutoPtr ControllerInfo; - TJsonSettings JsonSettings; - ui32 Timeout = 0; - -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonBSControllerInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) - {} - - void Bootstrap(const TActorContext& ctx) { - const auto& params(Event->Get()->Request.GetParams()); - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - InitConfig(params); - RequestBSControllerInfo(); - Become(&TThis::StateRequestedInfo, ctx, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } - - STATEFN(StateRequestedInfo) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvBlobStorage::TEvResponseControllerInfo, Handle); - hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - void Handle(TEvBlobStorage::TEvResponseControllerInfo::TPtr& ev) { - ControllerInfo = ev->Release(); - RequestDone(); - } - - void ReplyAndPassAway() { - TStringStream json; - if (ControllerInfo != nullptr) { - TProtoToJson::ProtoToJson(json, ControllerInfo->Record); - } else { - json << "null"; - } - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } - - void HandleTimeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Node(R"___( - - name: controller_id - in: query - description: storage controller identifier (tablet id) - required: true - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Storage controller information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about storage controller"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_cluster.h b/ydb/core/viewer/json_cluster.h deleted file mode 100644 index a4b6c0e86b81..000000000000 --- a/ydb/core/viewer/json_cluster.h +++ /dev/null @@ -1,542 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "json_pipe_req.h" -#include "viewer.h" -#include "viewer_probes.h" - -LWTRACE_USING(VIEWER_PROVIDER); - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; -using namespace NNodeWhiteboard; -using ::google::protobuf::FieldDescriptor; - -class TJsonCluster : public TViewerPipeClient { - using TThis = TJsonCluster; - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - THolder NodesInfo; - TMap SystemInfo; - TMap VDiskInfo; - TMap PDiskInfo; - TMap BSGroupInfo; - TMap TabletInfo; - THolder DescribeResult; - TSet NodesAlive; - TJsonSettings JsonSettings; - ui32 Timeout; - ui32 TenantsNumber = 0; - bool Tablets = false; - - struct TEventLog { - bool IsTimeout = false; - TInstant StartTime; - TInstant StartHandleListTenantsResponseTime; - TInstant StartHandleNodesInfoTime; - TInstant StartMergeBSGroupsTime; - TInstant StartMergeVDisksTime; - TInstant StartMergePDisksTime; - TInstant StartMergeTabletsTime; - TInstant StartResponseBuildingTime; - }; - TEventLog EventLog; -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonCluster(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Event(ev) - { - const auto& params(Event->Get()->Request.GetParams()); - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); - InitConfig(params); - Tablets = FromStringWithDefault(params.Get("tablets"), false); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - } - - void Bootstrap(const TActorContext& ) { - EventLog.StartTime = TActivationContext::Now(); - SendRequest(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); - RequestConsoleListTenants(); - Become(&TThis::StateRequested, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } - - void PassAway() override { - if (NodesInfo != nullptr) { - TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; - for (const auto& ni : NodesInfo->Nodes) { - if (ni.NodeId <= dynamicNameserviceConfig->MaxStaticNodeId) { - Send(TActivationContext::InterconnectProxy(ni.NodeId), new TEvents::TEvUnsubscribe); - } - } - } - TBase::PassAway(); - } - - void SendWhiteboardTabletStateRequest() { - THashSet filterTablets; - TIntrusivePtr domains = AppData()->DomainsInfo; - if (const auto& domain = domains->Domain) { - for (TTabletId id : domain->Coordinators) { - filterTablets.emplace(id); - } - for (TTabletId id : domain->Mediators) { - filterTablets.emplace(id); - } - for (TTabletId id : domain->TxAllocators) { - filterTablets.emplace(id); - } - filterTablets.emplace(domain->SchemeRoot); - filterTablets.emplace(domains->GetHive()); - } - filterTablets.emplace(MakeBSControllerID()); - filterTablets.emplace(MakeDefaultHiveID()); - filterTablets.emplace(MakeCmsID()); - filterTablets.emplace(MakeNodeBrokerID()); - filterTablets.emplace(MakeTenantSlotBrokerID()); - filterTablets.emplace(MakeConsoleID()); - const NKikimrSchemeOp::TPathDescription& pathDescription(DescribeResult->GetRecord().GetPathDescription()); - if (pathDescription.HasDomainDescription()) { - const NKikimrSubDomains::TDomainDescription& domainDescription(pathDescription.GetDomainDescription()); - for (TTabletId tabletId : domainDescription.GetProcessingParams().GetCoordinators()) { - filterTablets.emplace(tabletId); - } - for (TTabletId tabletId : domainDescription.GetProcessingParams().GetMediators()) { - filterTablets.emplace(tabletId); - } - if (domainDescription.HasDomainKey()) { - if (domainDescription.GetDomainKey().HasSchemeShard()) { - filterTablets.emplace(domainDescription.GetDomainKey().GetSchemeShard()); - } - } - } - - TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; - for (const auto& ni : NodesInfo->Nodes) { - if (ni.NodeId <= dynamicNameserviceConfig->MaxStaticNodeId) { - TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(ni.NodeId); - auto request = new TEvWhiteboard::TEvTabletStateRequest(); - for (TTabletId id: filterTablets) { - request->Record.AddFilterTabletId(id); - } - SendRequest(whiteboardServiceId, request, IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, ni.NodeId); - } - } - } - - void SendWhiteboardRequests() { - TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; - for (const auto& ni : NodesInfo->Nodes) { - TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(ni.NodeId); - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvSystemStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, ni.NodeId); - - if (ni.NodeId <= dynamicNameserviceConfig->MaxStaticNodeId) { - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvVDiskStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, ni.NodeId); - SendRequest(whiteboardServiceId,new TEvWhiteboard::TEvPDiskStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, ni.NodeId); - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvBSGroupStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, ni.NodeId); - } - } - if (Tablets) { - SendWhiteboardTabletStateRequest(); - } - } - - void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { - EventLog.StartHandleNodesInfoTime = TActivationContext::Now(); - NodesInfo = ev->Release(); - // before making requests to Whiteboard with the Tablets parameter, we need to review the TEvDescribeSchemeResult information - if (Tablets) { - THolder request = MakeHolder(); - if (!Event->Get()->UserToken.empty()) { - request->Record.SetUserToken(Event->Get()->UserToken); - } - NKikimrSchemeOp::TDescribePath* record = request->Record.MutableDescribePath(); - TIntrusivePtr domains = AppData()->DomainsInfo; - if (const auto& domain = domains->Domain) { - TString domainPath = "/" + domain->Name; - record->SetPath(domainPath); - } - record->MutableOptions()->SetReturnPartitioningInfo(false); - record->MutableOptions()->SetReturnPartitionConfig(false); - record->MutableOptions()->SetReturnChildren(false); - SendRequest(MakeTxProxyID(), request.Release()); - } else { - SendWhiteboardRequests(); - } - - RequestDone(); - } - - void Undelivered(TEvents::TEvUndelivered::TPtr &ev) { - ui32 nodeId = ev.Get()->Cookie; - switch (ev->Get()->SourceType) { - case TEvWhiteboard::EvSystemStateRequest: - if (SystemInfo.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvVDiskStateRequest: - if (VDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvVDiskStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvPDiskStateRequest: - if (PDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvPDiskStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvBSGroupStateRequest: - if (BSGroupInfo.emplace(nodeId, NKikimrWhiteboard::TEvBSGroupStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvTabletStateRequest: - if (TabletInfo.emplace(nodeId, NKikimrWhiteboard::TEvTabletStateResponse{}).second) { - RequestDone(); - } - break; - } - } - - void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr &ev) { - ui32 nodeId = ev->Get()->NodeId; - if (SystemInfo.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; - if (nodeId <= dynamicNameserviceConfig->MaxStaticNodeId) { - if (VDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvVDiskStateResponse{}).second) { - RequestDone(); - } - if (PDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvPDiskStateResponse{}).second) { - RequestDone(); - } - if (BSGroupInfo.emplace(nodeId, NKikimrWhiteboard::TEvBSGroupStateResponse{}).second) { - RequestDone(); - } - if (Tablets) { - if (TabletInfo.emplace(nodeId, NKikimrWhiteboard::TEvTabletStateResponse{}).second) { - RequestDone(); - } - } - } - } - - void Handle(TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - SystemInfo[nodeId] = std::move(ev->Get()->Record); - NodesAlive.insert(nodeId); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvVDiskStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - VDiskInfo[nodeId] = std::move(ev->Get()->Record); - NodesAlive.insert(nodeId); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvPDiskStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - PDiskInfo[nodeId] = std::move(ev->Get()->Record); - NodesAlive.insert(nodeId); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvBSGroupStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - BSGroupInfo[nodeId] = std::move(ev->Get()->Record); - NodesAlive.insert(nodeId); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvTabletStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - TabletInfo[nodeId] = std::move(ev->Get()->Record); - NodesAlive.insert(nodeId); - RequestDone(); - } - - void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { - EventLog.StartHandleListTenantsResponseTime = TActivationContext::Now(); - Ydb::Cms::ListDatabasesResult listTenantsResult; - ev->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); - TenantsNumber = listTenantsResult.paths().size(); - RequestDone(); - } - - void Handle(NSchemeShard::TEvSchemeShard::TEvDescribeSchemeResult::TPtr& ev) { - if (ev->Get()->GetRecord().GetStatus() == NKikimrScheme::StatusSuccess) { - DescribeResult = ev->Release(); - SendWhiteboardRequests(); - } - RequestDone(); - } - - void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { - if (ev->Get()->Status != NKikimrProto::OK) { - RequestDone(); - } - } - - STATEFN(StateRequested) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvInterconnect::TEvNodesInfo, Handle); - hFunc(TEvWhiteboard::TEvSystemStateResponse, Handle); - hFunc(TEvWhiteboard::TEvVDiskStateResponse, Handle); - hFunc(TEvWhiteboard::TEvPDiskStateResponse, Handle); - hFunc(TEvWhiteboard::TEvBSGroupStateResponse, Handle); - hFunc(TEvWhiteboard::TEvTabletStateResponse, Handle); - hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); - hFunc(NSchemeShard::TEvSchemeShard::TEvDescribeSchemeResult, Handle); - hFunc(TEvents::TEvUndelivered, Undelivered); - hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); - hFunc(TEvTabletPipe::TEvClientConnected, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - NKikimrWhiteboard::TEvBSGroupStateResponse MergedBSGroupInfo; - NKikimrWhiteboard::TEvVDiskStateResponse MergedVDiskInfo; - NKikimrWhiteboard::TEvPDiskStateResponse MergedPDiskInfo; - NKikimrWhiteboard::TEvTabletStateResponse MergedTabletInfo; - TMap VDisksIndex; - TMap, const NKikimrWhiteboard::TPDiskStateInfo&> PDisksIndex; - - void ReplyAndPassAway() { - EventLog.StartMergeBSGroupsTime = TActivationContext::Now(); - MergeWhiteboardResponses(MergedBSGroupInfo, BSGroupInfo); - EventLog.StartMergeVDisksTime = TActivationContext::Now(); - MergeWhiteboardResponses(MergedVDiskInfo, VDiskInfo); - EventLog.StartMergePDisksTime = TActivationContext::Now(); - MergeWhiteboardResponses(MergedPDiskInfo, PDiskInfo); - - EventLog.StartMergeTabletsTime = TActivationContext::Now(); - THashSet tablets; - if (Tablets) { - MergeWhiteboardResponses(MergedTabletInfo, TabletInfo); - } - - EventLog.StartResponseBuildingTime = TActivationContext::Now(); - if (Tablets) { - TIntrusivePtr domains = AppData()->DomainsInfo; - if (const auto& domain = domains->Domain) { - tablets.emplace(MakeBSControllerID()); - tablets.emplace(MakeDefaultHiveID()); - tablets.emplace(MakeCmsID()); - tablets.emplace(MakeNodeBrokerID()); - tablets.emplace(MakeTenantSlotBrokerID()); - tablets.emplace(MakeConsoleID()); - tablets.emplace(domain->SchemeRoot); - tablets.emplace(domains->GetHive()); - for (TTabletId id : domain->Coordinators) { - tablets.emplace(id); - } - for (TTabletId id : domain->Mediators) { - tablets.emplace(id); - } - for (TTabletId id : domain->TxAllocators) { - tablets.emplace(id); - } - } - - if (DescribeResult) { - const NKikimrSchemeOp::TPathDescription& pathDescription(DescribeResult->GetRecord().GetPathDescription()); - if (pathDescription.HasDomainDescription()) { - const NKikimrSubDomains::TDomainDescription& domainDescription(pathDescription.GetDomainDescription()); - for (TTabletId tabletId : domainDescription.GetProcessingParams().GetCoordinators()) { - tablets.emplace(tabletId); - } - for (TTabletId tabletId : domainDescription.GetProcessingParams().GetMediators()) { - tablets.emplace(tabletId); - } - if (domainDescription.HasDomainKey()) { - if (domainDescription.GetDomainKey().HasSchemeShard()) { - tablets.emplace(domainDescription.GetDomainKey().GetSchemeShard()); - } - } - } - } - } - - ui64 totalStorageSize = 0; - ui64 availableStorageSize = 0; - - for (auto& element : TWhiteboardInfo::GetElementsField(MergedPDiskInfo)) { - if (element.HasTotalSize() && element.HasAvailableSize()) { - totalStorageSize += element.GetTotalSize(); - availableStorageSize += element.GetAvailableSize(); - } - element.SetStateFlag(GetWhiteboardFlag(GetPDiskStateFlag(element))); - element.SetOverall(GetWhiteboardFlag(GetPDiskOverallFlag(element))); - PDisksIndex.emplace(TWhiteboardInfo::GetElementKey(element), element); - } - for (auto& element : TWhiteboardInfo::GetElementsField(MergedVDiskInfo)) { - element.SetOverall(GetWhiteboardFlag(GetVDiskOverallFlag(element))); - VDisksIndex.emplace(TWhiteboardInfo::GetElementKey(element), element); - } - NKikimrViewer::EFlag flag = NKikimrViewer::Grey; - for (const auto& element : TWhiteboardInfo::GetElementsField(MergedBSGroupInfo)) { - flag = Max(flag, GetBSGroupOverallFlag(element, VDisksIndex, PDisksIndex)); - } - ui32 numberOfCpus = 0; - double loadAverage = 0; - THashSet dataCenters; - THashSet versions; - THashSet hosts; - THashMap names; - for (const auto& [nodeId, sysInfo] : SystemInfo) { - if (sysInfo.SystemStateInfoSize() > 0) { - const NKikimrWhiteboard::TSystemStateInfo& systemState = sysInfo.GetSystemStateInfo(0); - if (systemState.HasNumberOfCpus() && (!systemState.HasHost() || hosts.emplace(systemState.GetHost()).second)) { - numberOfCpus += systemState.GetNumberOfCpus(); - if (systemState.LoadAverageSize() > 0) { - loadAverage += systemState.GetLoadAverage(0); - } - } - if (systemState.HasDataCenter()) { - dataCenters.insert(systemState.GetDataCenter()); - } - if (systemState.HasVersion()) { - versions.insert(systemState.GetVersion()); - } - if (systemState.HasClusterName()) { - names[systemState.GetClusterName()]++; - } - } - } - - NKikimrViewer::TClusterInfo pbCluster; - - if (Tablets) { - for (const NKikimrWhiteboard::TTabletStateInfo& tabletInfo : MergedTabletInfo.GetTabletStateInfo()) { - if (tablets.contains(tabletInfo.GetTabletId())) { - NKikimrWhiteboard::TTabletStateInfo* tablet = pbCluster.AddSystemTablets(); - tablet->CopyFrom(tabletInfo); - auto tabletFlag = GetWhiteboardFlag(GetFlagFromTabletState(tablet->GetState())); - tablet->SetOverall(tabletFlag); - flag = Max(flag, GetViewerFlag(tabletFlag)); - } - } - pbCluster.SetTablets(MergedTabletInfo.TabletStateInfoSize()); - } - pbCluster.SetTenants(TenantsNumber); - - pbCluster.SetOverall(flag); - if (NodesInfo != nullptr) { - pbCluster.SetNodesTotal(NodesInfo->Nodes.size()); - pbCluster.SetNodesAlive(NodesAlive.size()); - } - pbCluster.SetNumberOfCpus(numberOfCpus); - pbCluster.SetLoadAverage(loadAverage); - pbCluster.SetStorageTotal(totalStorageSize); - pbCluster.SetStorageUsed(totalStorageSize - availableStorageSize); - pbCluster.SetHosts(hosts.size()); - TIntrusivePtr domains = AppData()->DomainsInfo; - if (const auto& domain = domains->Domain) { - TString domainName = "/" + domain->Name; - pbCluster.SetDomain(domainName); - } - for (const TString& dc : dataCenters) { - pbCluster.AddDataCenters(dc); - } - for (const TString& version : versions) { - pbCluster.AddVersions(version); - } - auto itMax = std::max_element(names.begin(), names.end(), [](const auto& a, const auto& b) { - return a.second < b.second; - }); - if (itMax != names.end()) { - pbCluster.SetName(itMax->first); - } - - TStringStream json; - TProtoToJson::ProtoToJson(json, pbCluster, JsonSettings); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), std::move(json.Str())), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - - const TInstant now = TActivationContext::Now(); - LWPROBE(ViewerClusterHandler, TBase::SelfId().NodeId(), Tablets, EventLog.IsTimeout, - EventLog.StartTime.MilliSeconds(), - (now - EventLog.StartTime).MilliSeconds(), - (EventLog.StartHandleListTenantsResponseTime - EventLog.StartTime).MilliSeconds(), - (EventLog.StartHandleNodesInfoTime - EventLog.StartTime).MilliSeconds(), - (EventLog.StartMergeBSGroupsTime - EventLog.StartTime).MilliSeconds(), - (EventLog.StartMergeVDisksTime - EventLog.StartMergeBSGroupsTime).MilliSeconds(), - (EventLog.StartMergePDisksTime - EventLog.StartMergeVDisksTime).MilliSeconds(), - (EventLog.StartMergeTabletsTime - EventLog.StartMergePDisksTime).MilliSeconds(), - (EventLog.StartResponseBuildingTime - EventLog.StartMergeTabletsTime).MilliSeconds(), - (now - EventLog.StartResponseBuildingTime).MilliSeconds() - ); - - PassAway(); - } - - void HandleTimeout() { - EventLog.IsTimeout = true; - ReplyAndPassAway(); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: tablets - in: query - description: return system tablets state - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Cluster information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about cluster"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_describe_consumer.h b/ydb/core/viewer/json_describe_consumer.h deleted file mode 100644 index 7771e05b361f..000000000000 --- a/ydb/core/viewer/json_describe_consumer.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once -#include -#include -#include "json_local_rpc.h" - -namespace NKikimr { -namespace NViewer { - -using TDescribeConsumerRpc = TJsonLocalRpc; - -class TJsonDescribeConsumer : public TDescribeConsumerRpc { -public: - using TBase = TDescribeConsumerRpc; - - TJsonDescribeConsumer(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : TBase(viewer, ev) - {} - - void Bootstrap() override { - if (Event->Get()->Request.GetMethod() != HTTP_METHOD_GET) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only GET method is allowed")); - } - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } else if (params.Has("database_path")) { - Database = params.Get("database_path"); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("consumer")) { - Request.set_consumer(params.Get("consumer")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'consumer' is required")); - } - - if (params.Has("path")) { - Request.set_path(params.Get("path")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'path' is required")); - } - - if (params.Has("include_stats")) { - Request.set_include_stats(FromStringWithDefault(params.Get("include_stats"), false)); - } - - TBase::Bootstrap(); - } -}; - -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - viewer - summary: Topic schema detailed information - description: Returns detailed information about topic - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: consumer - in: query - description: consumer name - required: true - type: string - - name: include_stats - in: query - description: include stat flag - required: false - type: bool - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; -} - -} -} diff --git a/ydb/core/viewer/json_describe_topic.h b/ydb/core/viewer/json_describe_topic.h deleted file mode 100644 index 2a7a8fb418dc..000000000000 --- a/ydb/core/viewer/json_describe_topic.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once -#include -#include -#include "json_local_rpc.h" - -namespace NKikimr { -namespace NViewer { - -using TDescribeTopicRpc = TJsonLocalRpc; - -class TJsonDescribeTopic : public TDescribeTopicRpc { -public: - using TBase = TDescribeTopicRpc; - - TJsonDescribeTopic(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : TBase(viewer, ev) - {} - - void Bootstrap() override { - if (Event->Get()->Request.GetMethod() != HTTP_METHOD_GET) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only GET method is allowed")); - } - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } else if (params.Has("database_path")) { - Database = params.Get("database_path"); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("path")) { - Request.set_path(params.Get("path")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'path' is required")); - } - - if (params.Has("include_stats")) { - Request.set_include_stats(FromStringWithDefault(params.Get("include_stats"), false)); - } - - TBase::Bootstrap(); - } -}; - -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - viewer - summary: Topic schema detailed information - description: Returns detailed information about topic - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: path - in: query - description: schema path - required: true - type: string - - name: include_stats - in: query - description: include stat flag - required: false - type: bool - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; -} - -} -} diff --git a/ydb/core/viewer/json_handlers.cpp b/ydb/core/viewer/json_handlers.cpp new file mode 100644 index 000000000000..7f7b070dd37b --- /dev/null +++ b/ydb/core/viewer/json_handlers.cpp @@ -0,0 +1,47 @@ +#include "json_handlers.h" +#include + +namespace NKikimr::NViewer { + +TSimpleYamlBuilder::TSimpleYamlBuilder(TInitializer initializer) { + Method = Root[TString(initializer.Method)]; + if (initializer.Url) { + Method["tags"].push_back(TString(initializer.Url.After('/').Before('/'))); + } + if (initializer.Tag) { + Method["tags"].push_back(TString(initializer.Tag)); + } + if (initializer.Summary) { + Method["summary"] = TString(initializer.Summary); + } + if (initializer.Description) { + Method["description"] = TString(initializer.Description); + } +} + +void TSimpleYamlBuilder::SetParameters(YAML::Node parameters) { + Method["parameters"] = parameters; +} + +void TSimpleYamlBuilder::AddParameter(TParameter parameter) { + YAML::Node param; + param["in"] = "query"; + param["name"] = TString(parameter.Name); + if (parameter.Description) { + param["description"] = TString(parameter.Description); + } + if (parameter.Type) { + param["type"] = TString(parameter.Type); + } + if (parameter.Default) { + param["default"] = TString(parameter.Default); + } + param["required"] = parameter.Required; + Method["parameters"].push_back(param); +} + +void TSimpleYamlBuilder::SetResponseSchema(YAML::Node schema) { + Method["responses"]["200"]["content"]["application/json"]["schema"] = schema; +} + +} diff --git a/ydb/core/viewer/json_handlers.h b/ydb/core/viewer/json_handlers.h index bfe329ad9385..0e0aeb52a890 100644 --- a/ydb/core/viewer/json_handlers.h +++ b/ydb/core/viewer/json_handlers.h @@ -1,7 +1,7 @@ #pragma once - #include "viewer.h" -#include +#include +#include namespace NKikimr::NViewer { @@ -9,53 +9,36 @@ class TJsonHandlerBase { public: virtual ~TJsonHandlerBase() = default; virtual IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) = 0; - virtual YAML::Node GetResponseJsonSchema() = 0; - virtual TString GetRequestSummary() = 0; - virtual TString GetRequestDescription() = 0; - virtual YAML::Node GetRequestParameters() = 0; virtual YAML::Node GetRequestSwagger() = 0; }; template class TJsonHandler : public TJsonHandlerBase { public: - IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) override { - return new ActorRequestType(viewer, event); - } + YAML::Node Swagger; - YAML::Node GetResponseJsonSchema() override { - static YAML::Node jsonSchema = TJsonRequestSchema::GetSchema(); - return jsonSchema; - } + TJsonHandler(YAML::Node swagger) + : Swagger(swagger) + {} - TString GetRequestSummary() override { - static TString summary = TJsonRequestSummary::GetSummary(); - return summary; - } - - TString GetRequestDescription() override { - static TString description = TJsonRequestDescription::GetDescription(); - return description; - } - - YAML::Node GetRequestParameters() override { - static YAML::Node parameters = TJsonRequestParameters::GetParameters(); - return parameters; + IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) override { + return new ActorRequestType(viewer, event); } YAML::Node GetRequestSwagger() override { - static YAML::Node swagger = TJsonRequestSwagger::GetSwagger(); - return swagger; + return Swagger; } }; struct TJsonHandlers { std::vector JsonHandlersList; THashMap> JsonHandlersIndex; + std::map Capabilities; - void AddHandler(const TString& name, TJsonHandlerBase* handler) { + void AddHandler(const TString& name, TJsonHandlerBase* handler, int version = 1) { JsonHandlersList.push_back(name); JsonHandlersIndex[name] = std::shared_ptr(handler); + Capabilities[name] = version; } TJsonHandlerBase* FindHandler(const TString& name) const { @@ -65,6 +48,45 @@ struct TJsonHandlers { } return it->second.get(); } + + int GetCapabilityVersion(const TString& name) const { + auto it = Capabilities.find(name); + if (it == Capabilities.end()) { + return 0; + } + return it->second; + } +}; + +class TSimpleYamlBuilder { +public: + struct TInitializer { + TStringBuf Method; + TStringBuf Tag; + TStringBuf Url; + TStringBuf Summary; + TStringBuf Description; + }; + + struct TParameter { + TStringBuf Name; + TStringBuf Description; + TStringBuf Type; + TStringBuf Default; + bool Required = false; + }; + + YAML::Node Root; + YAML::Node Method; + + TSimpleYamlBuilder(TInitializer initializer); + void SetParameters(YAML::Node parameters); + void AddParameter(TParameter parameter); + void SetResponseSchema(YAML::Node schema); + + operator YAML::Node() { + return Root; + } }; } // namespace NKikimr::NViewer diff --git a/ydb/core/viewer/json_handlers_browse.cpp b/ydb/core/viewer/json_handlers_browse.cpp new file mode 100644 index 000000000000..c9ec41656d8f --- /dev/null +++ b/ydb/core/viewer/json_handlers_browse.cpp @@ -0,0 +1,37 @@ +#include "json_handlers.h" +#include "browse.h" +#include "browse_db.h" +#include "browse_pq.h" +#include "viewer_browse.h" +#include "viewer_content.h" +#include "viewer_metainfo.h" + +namespace NKikimr::NViewer { + +void SetupDBVirtualHandlers(IViewer* viewer) { + viewer->RegisterVirtualHandler( + NKikimrViewer::EObjectType::Table, + [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { + return new NViewerDB::TBrowseTable(owner, browseContext); + }); +} + +void InitViewerMetaInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/metainfo", new TJsonHandler(TJsonMetaInfo::GetSwagger())); +} + +void InitViewerBrowseJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/browse", new TJsonHandler(TJsonBrowse::GetSwagger())); +} + +void InitViewerContentJsonHandler(TJsonHandlers &jsonHandlers) { + jsonHandlers.AddHandler("/viewer/content", new TJsonHandler(TJsonContent::GetSwagger())); +} + +void InitViewerBrowseJsonHandlers(TJsonHandlers& jsonHandlers) { + InitViewerMetaInfoJsonHandler(jsonHandlers); + InitViewerBrowseJsonHandler(jsonHandlers); + InitViewerContentJsonHandler(jsonHandlers); +} + +} diff --git a/ydb/core/viewer/json_handlers_operation.cpp b/ydb/core/viewer/json_handlers_operation.cpp index 4e9c90da6d79..7375d177983b 100644 --- a/ydb/core/viewer/json_handlers_operation.cpp +++ b/ydb/core/viewer/json_handlers_operation.cpp @@ -1,17 +1,32 @@ #include "json_handlers.h" - -#include "operation_get.h" -#include "operation_list.h" #include "operation_cancel.h" #include "operation_forget.h" +#include "operation_get.h" +#include "operation_list.h" namespace NKikimr::NViewer { -void InitOperationJsonHandlers(TJsonHandlers& jsonHandlers) { - jsonHandlers.AddHandler("/operation/get", new TJsonHandler); - jsonHandlers.AddHandler("/operation/list", new TJsonHandler); - jsonHandlers.AddHandler("/operation/cancel", new TJsonHandler); - jsonHandlers.AddHandler("/operation/forget", new TJsonHandler); +void InitOperationGetJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/operation/get", new TJsonHandler(TOperationGet::GetSwagger())); +} + +void InitOperationListJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/operation/list", new TJsonHandler(TOperationList::GetSwagger())); +} + +void InitOperationCancelJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/operation/cancel", new TJsonHandler(TOperationCancel::GetSwagger())); } +void InitOperationForgetJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/operation/forget", new TJsonHandler(TOperationForget::GetSwagger())); } + +void InitOperationJsonHandlers(TJsonHandlers& jsonHandlers) { + InitOperationGetJsonHandler(jsonHandlers); + InitOperationListJsonHandler(jsonHandlers); + InitOperationCancelJsonHandler(jsonHandlers); + InitOperationForgetJsonHandler(jsonHandlers); +} + +} // namespace NKikimr::NViewer diff --git a/ydb/core/viewer/json_handlers_pdisk.cpp b/ydb/core/viewer/json_handlers_pdisk.cpp index ef99307e25be..55845fc83cad 100644 --- a/ydb/core/viewer/json_handlers_pdisk.cpp +++ b/ydb/core/viewer/json_handlers_pdisk.cpp @@ -1,19 +1,26 @@ -#include -#include - #include "json_handlers.h" - -#include "json_pdisk_restart.h" #include "pdisk_info.h" +#include "pdisk_restart.h" #include "pdisk_status.h" - namespace NKikimr::NViewer { +void InitPDiskInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/pdisk/info", new TJsonHandler(TPDiskInfo::GetSwagger())); +} + +void InitPDiskRestartJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/pdisk/restart", new TJsonHandler(TJsonPDiskRestart::GetSwagger())); +} + +void InitPDiskStatusJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/pdisk/status", new TJsonHandler(TPDiskStatus::GetSwagger())); +} + void InitPDiskJsonHandlers(TJsonHandlers& jsonHandlers) { - jsonHandlers.AddHandler("/pdisk/info", new TJsonHandler); - jsonHandlers.AddHandler("/pdisk/restart", new TJsonHandler); - jsonHandlers.AddHandler("/pdisk/status", new TJsonHandler); + InitPDiskInfoJsonHandler(jsonHandlers); + InitPDiskRestartJsonHandler(jsonHandlers); + InitPDiskStatusJsonHandler(jsonHandlers); } } diff --git a/ydb/core/viewer/json_handlers_pq.cpp b/ydb/core/viewer/json_handlers_pq.cpp new file mode 100644 index 000000000000..d3503b95b7f9 --- /dev/null +++ b/ydb/core/viewer/json_handlers_pq.cpp @@ -0,0 +1,29 @@ +#include "viewer.h" +#include "browse_pq.h" + +namespace NKikimr::NViewer { + +void SetupPQVirtualHandlers(IViewer* viewer) { + viewer->RegisterVirtualHandler( + NKikimrViewer::EObjectType::Root, + [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { + return new NViewerPQ::TBrowseRoot(owner, browseContext); + }); + viewer->RegisterVirtualHandler( + NKikimrViewer::EObjectType::Consumers, + [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { + return new NViewerPQ::TBrowseConsumers(owner, browseContext); + }); + viewer->RegisterVirtualHandler( + NKikimrViewer::EObjectType::Consumer, + [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { + return new NViewerPQ::TBrowseConsumer(owner, browseContext); + }); + viewer->RegisterVirtualHandler( + NKikimrViewer::EObjectType::Topic, + [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { + return new NViewerPQ::TBrowseTopic(owner, browseContext); + }); +} + +} diff --git a/ydb/core/viewer/json_handlers_query.cpp b/ydb/core/viewer/json_handlers_query.cpp new file mode 100644 index 000000000000..a79c05b54c1f --- /dev/null +++ b/ydb/core/viewer/json_handlers_query.cpp @@ -0,0 +1,20 @@ +#include "json_handlers.h" +#include "query_execute_script.h" +#include "query_fetch_script.h" + +namespace NKikimr::NViewer { + +void InitQueryExecuteScriptJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/query/script/execute", new TJsonHandler(TQueryExecuteScript::GetSwagger())); +} + +void InitQueryFetchScriptJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/query/script/fetch", new TJsonHandler(TQueryFetchScript::GetSwagger())); +} + +void InitQueryJsonHandlers(TJsonHandlers& jsonHandlers) { + InitQueryExecuteScriptJsonHandler(jsonHandlers); + InitQueryFetchScriptJsonHandler(jsonHandlers); +} + +} // namespace NKikimr::NViewer diff --git a/ydb/core/viewer/json_handlers_scheme.cpp b/ydb/core/viewer/json_handlers_scheme.cpp index c5dc07f28b28..0445940e641c 100644 --- a/ydb/core/viewer/json_handlers_scheme.cpp +++ b/ydb/core/viewer/json_handlers_scheme.cpp @@ -1,11 +1,14 @@ #include "json_handlers.h" - #include "scheme_directory.h" namespace NKikimr::NViewer { +void InitSchemeDirectoryHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/scheme/directory", new TJsonSchemeDirectoryHandler(), 2); +} + void InitSchemeJsonHandlers(TJsonHandlers& jsonHandlers) { - jsonHandlers.AddHandler("/scheme/directory", new TJsonSchemeDirectoryHandler()); + InitSchemeDirectoryHandler(jsonHandlers); } } diff --git a/ydb/core/viewer/json_handlers_storage.cpp b/ydb/core/viewer/json_handlers_storage.cpp new file mode 100644 index 000000000000..750da437452e --- /dev/null +++ b/ydb/core/viewer/json_handlers_storage.cpp @@ -0,0 +1,14 @@ +#include "json_handlers.h" +#include "storage_groups.h" + +namespace NKikimr::NViewer { + +void InitStorageGroupsJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/storage/groups", new TJsonHandler(TStorageGroups::GetSwagger()), 6); +} + +void InitStorageJsonHandlers(TJsonHandlers& jsonHandlers) { + InitStorageGroupsJsonHandler(jsonHandlers); +} + +} diff --git a/ydb/core/viewer/json_handlers_vdisk.cpp b/ydb/core/viewer/json_handlers_vdisk.cpp index 49c869692f08..633bf9c80566 100644 --- a/ydb/core/viewer/json_handlers_vdisk.cpp +++ b/ydb/core/viewer/json_handlers_vdisk.cpp @@ -1,20 +1,56 @@ -#include -#include - #include "json_handlers.h" - -#include "json_vdiskstat.h" -#include "json_getblob.h" -#include "json_blobindexstat.h" -#include "json_vdisk_evict.h" +#include "vdisk_vdiskstat.h" +#include "vdisk_blobindexstat.h" +#include "vdisk_getblob.h" +#include "vdisk_evict.h" namespace NKikimr::NViewer { +void InitVDiskStatJsonHandler(TJsonHandlers& handlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "vdisk", + .Summary = "VDisk statistic", + .Description = "VDisk statistic", + }); + yaml.SetParameters(TJsonVDiskStat::GetParameters()); + yaml.SetResponseSchema(TJsonVDiskStat::GetSchema()); + handlers.AddHandler("/vdisk/vdiskstat", new TJsonHandler(yaml)); +} + +void InitVDiskGetBlobJsonHandler(TJsonHandlers& handlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "vdisk", + .Summary = "Get blob from VDisk", + .Description = "Get blob from VDisk", + }); + yaml.SetParameters(TJsonGetBlob::GetParameters()); + yaml.SetResponseSchema(TJsonGetBlob::GetSchema()); + handlers.AddHandler("/vdisk/getblob", new TJsonHandler(yaml)); +} + +void InitVDiskBlobIndexStatJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "vdisk", + .Summary = "Get logoblob index stat from VDisk", + .Description = "Get logoblob index stat from VDisk", + }); + yaml.SetParameters(TJsonBlobIndexStat::GetParameters()); + yaml.SetResponseSchema(TJsonBlobIndexStat::GetSchema()); + jsonHandlers.AddHandler("/vdisk/blobindexstat", new TJsonHandler(yaml)); +} + +void InitVDiskEvictJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/vdisk/evict", new TJsonHandler(TJsonVDiskEvict::GetSwagger())); +} + void InitVDiskJsonHandlers(TJsonHandlers& jsonHandlers) { - jsonHandlers.AddHandler("/vdisk/vdiskstat", new TJsonHandler); - jsonHandlers.AddHandler("/vdisk/getblob", new TJsonHandler); - jsonHandlers.AddHandler("/vdisk/blobindexstat", new TJsonHandler); - jsonHandlers.AddHandler("/vdisk/evict", new TJsonHandler); + InitVDiskStatJsonHandler(jsonHandlers); + InitVDiskGetBlobJsonHandler(jsonHandlers); + InitVDiskBlobIndexStatJsonHandler(jsonHandlers); + InitVDiskEvictJsonHandler(jsonHandlers); } } diff --git a/ydb/core/viewer/json_handlers_viewer.cpp b/ydb/core/viewer/json_handlers_viewer.cpp index ff55ada322b4..56180de9dab3 100644 --- a/ydb/core/viewer/json_handlers_viewer.cpp +++ b/ydb/core/viewer/json_handlers_viewer.cpp @@ -1,89 +1,314 @@ -#include - #include "json_handlers.h" - -#include "json_nodelist.h" -#include "json_nodeinfo.h" -#include "json_vdiskinfo.h" -#include "json_pdiskinfo.h" -#include "json_describe.h" -#include "json_describe_topic.h" -#include "json_describe_consumer.h" -#include "json_hotkeys.h" -#include "json_sysinfo.h" -#include "json_tabletinfo.h" -#include "json_hiveinfo.h" -#include "json_bsgroupinfo.h" -#include "json_bscontrollerinfo.h" -#include "json_config.h" -#include "json_counters.h" -#include "json_topicinfo.h" -#include "json_pqconsumerinfo.h" -#include "json_tabletcounters.h" -#include "json_storage.h" -#include "json_storage_usage.h" -#include "json_metainfo.h" -#include "json_browse.h" -#include "json_cluster.h" -#include "json_content.h" -#include "json_labeledcounters.h" -#include "json_tenants.h" -#include "json_hivestats.h" -#include "json_tenantinfo.h" -#include "json_whoami.h" -#include "json_query.h" -#include "json_netinfo.h" -#include "json_compute.h" -#include "json_healthcheck.h" -#include "json_nodes.h" -#include "json_acl.h" -#include "json_graph.h" -#include "json_render.h" -#include "json_autocomplete.h" -#include "check_access.h" +#include "viewer_acl.h" +#include "viewer_autocomplete.h" +#include "viewer_bscontrollerinfo.h" +#include "viewer_capabilities.h" +#include "viewer_check_access.h" +#include "viewer_cluster.h" +#include "viewer_compute.h" +#include "viewer_config.h" +#include "viewer_counters.h" +#include "viewer_describe_consumer.h" +#include "viewer_describe.h" +#include "viewer_describe_topic.h" +#include "viewer_feature_flags.h" +#include "viewer_graph.h" +#include "viewer_healthcheck.h" +#include "viewer_hiveinfo.h" +#include "viewer_hivestats.h" +#include "viewer_hotkeys.h" +#include "viewer_labeled_counters.h" +#include "viewer_netinfo.h" +#include "viewer_nodelist.h" +#include "viewer_nodes.h" +#include "viewer_pqconsumerinfo.h" +#include "viewer_query.h" +#include "viewer_render.h" +#include "viewer_storage.h" +#include "viewer_storage_usage.h" +#include "viewer_tabletcounters.h" +#include "viewer_tenantinfo.h" +#include "viewer_tenants.h" +#include "viewer_topicinfo.h" +#include "viewer_whoami.h" namespace NKikimr::NViewer { +TBSGroupState GetBSGroupOverallStateWithoutLatency( + const NKikimrWhiteboard::TBSGroupStateInfo& info, + const TMap& vDisksIndex, + const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { + + TBSGroupState groupState; + groupState.Overall = NKikimrViewer::EFlag::Grey; + + const auto& vDiskIds = info.GetVDiskIds(); + std::unordered_map failedRings; + std::unordered_map failedDomains; + TVector vDiskFlags; + vDiskFlags.reserve(vDiskIds.size()); + for (auto iv = vDiskIds.begin(); iv != vDiskIds.end(); ++iv) { + const NKikimrBlobStorage::TVDiskID& vDiskId = *iv; + NKikimrViewer::EFlag flag = NKikimrViewer::EFlag::Grey; + auto ie = vDisksIndex.find(vDiskId); + if (ie != vDisksIndex.end()) { + auto pDiskId = std::make_pair(ie->second.GetNodeId(), ie->second.GetPDiskId()); + auto ip = pDisksIndex.find(pDiskId); + if (ip != pDisksIndex.end()) { + const NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo(ip->second); + flag = Max(flag, GetPDiskOverallFlag(pDiskInfo)); + } else { + flag = NKikimrViewer::EFlag::Red; + } + const NKikimrWhiteboard::TVDiskStateInfo& vDiskInfo(ie->second); + flag = Max(flag, GetVDiskOverallFlag(vDiskInfo)); + if (vDiskInfo.GetDiskSpace() > NKikimrWhiteboard::EFlag::Green) { + groupState.SpaceProblems++; + } + } else { + flag = NKikimrViewer::EFlag::Red; + } + vDiskFlags.push_back(flag); + if (flag == NKikimrViewer::EFlag::Red || flag == NKikimrViewer::EFlag::Blue) { + groupState.MissingDisks++; + ++failedRings[vDiskId.GetRing()]; + ++failedDomains[vDiskId.GetDomain()]; + } + groupState.Overall = Max(groupState.Overall, flag); + } + + groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); // without failed rings we only allow to raise group status up to Blue/Yellow + TString erasure = info.GetErasureSpecies(); + if (erasure == TErasureType::ErasureSpeciesName(TErasureType::ErasureNone)) { + if (!failedDomains.empty()) { + groupState.Overall = NKikimrViewer::EFlag::Red; + } + } else if (erasure == TErasureType::ErasureSpeciesName(TErasureType::ErasureMirror3dc)) { + if (failedRings.size() > 2) { + groupState.Overall = NKikimrViewer::EFlag::Red; + } else if (failedRings.size() == 2) { // TODO: check for 1 ring - 1 domain rule + groupState.Overall = NKikimrViewer::EFlag::Orange; + } else if (failedRings.size() > 0) { + groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); + } + } else if (erasure == TErasureType::ErasureSpeciesName(TErasureType::Erasure4Plus2Block)) { + if (failedDomains.size() > 2) { + groupState.Overall = NKikimrViewer::EFlag::Red; + } else if (failedDomains.size() > 1) { + groupState.Overall = NKikimrViewer::EFlag::Orange; + } else if (failedDomains.size() > 0) { + groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); + } + } + return groupState; +} + +NKikimrViewer::EFlag GetBSGroupOverallFlagWithoutLatency( + const NKikimrWhiteboard::TBSGroupStateInfo& info, + const TMap& vDisksIndex, + const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { + return GetBSGroupOverallStateWithoutLatency(info, vDisksIndex, pDisksIndex).Overall; +} + +TBSGroupState GetBSGroupOverallState( + const NKikimrWhiteboard::TBSGroupStateInfo& info, + const TMap& vDisksIndex, + const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { + TBSGroupState state = GetBSGroupOverallStateWithoutLatency(info, vDisksIndex, pDisksIndex); + if (info.HasLatency()) { + state.Overall = Max(state.Overall, Min(NKikimrViewer::EFlag::Yellow, GetViewerFlag(info.GetLatency()))); + } + return state; +} + +NKikimrViewer::EFlag GetBSGroupOverallFlag( + const NKikimrWhiteboard::TBSGroupStateInfo& info, + const TMap& vDisksIndex, + const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { + return GetBSGroupOverallState(info, vDisksIndex, pDisksIndex).Overall; +} + +void InitViewerCapabilitiesJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Viewer capabilities", + .Description = "Viewer capabilities", + }); + jsonHandlers.AddHandler("/viewer/capabilities", new TJsonHandler(yaml)); +} + +void InitViewerNodelistJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/nodelist", new TJsonHandler(TJsonNodeList::GetSwagger())); +} + +void InitViewerNodeInfoJsonHandler(TJsonHandlers& jsonHandlers); +void InitViewerSysInfoJsonHandler(TJsonHandlers& jsonHandlers); +void InitViewerVDiskInfoJsonHandler(TJsonHandlers& jsonHandlers); +void InitViewerPDiskInfoJsonHandler(TJsonHandlers& jsonHandlers); +void InitViewerTabletInfoJsonHandler(TJsonHandlers& jsonHandlers); + +void InitViewerDescribeJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/describe", new TJsonHandler(TJsonDescribe::GetSwagger())); +} + +void InitViewerDescribeTopicJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/describe_topic", new TJsonHandler(TJsonDescribeTopic::GetSwagger())); +} + +void InitViewerDescribeConsumerJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/describe_consumer", new TJsonHandler(TJsonDescribeConsumer::GetSwagger())); +} + +void InitViewerHotkeysJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/hotkeys", new TJsonHandler(TJsonHotkeys::GetSwagger())); +} + +void InitViewerHiveInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/hiveinfo", new TJsonHandler(TJsonHiveInfo::GetSwagger())); +} + +void InitViewerBSGroupInfoJsonHandler(TJsonHandlers& jsonHandlers); + +void InitViewerBSControllerInfoJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/bscontrollerinfo", new TJsonHandler(TJsonBSControllerInfo::GetSwagger())); +} + +void InitViewerConfigJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/config", new TJsonHandler(TJsonConfig::GetSwagger())); +} + +void InitViewerCountersJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/counters", new TJsonHandler(TJsonCounters::GetSwagger())); +} + +void InitViewerTopicInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/topicinfo", new TJsonHandler(TJsonTopicInfo::GetSwagger())); +} + +void InitViewerPQConsumerInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/pqconsumerinfo", new TJsonHandler(TJsonPQConsumerInfo::GetSwagger())); +} + +void InitViewerTabletCountersJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/tabletcounters", new TJsonHandler(TJsonTabletCounters::GetSwagger())); +} + +void InitViewerStorageJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/storage", new TJsonHandler(TJsonStorage::GetSwagger())); +} + +void InitViewerStorageUsageJsonHandler(TJsonHandlers &handlers) { + handlers.AddHandler("/viewer/storage_usage", new TJsonHandler(TJsonStorageUsage::GetSwagger())); +} + +void InitViewerClusterJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/cluster", new TJsonHandler(TJsonCluster::GetSwagger()), 4); +} + +void InitViewerLabeledCountersJsonHandler(TJsonHandlers &handlers) { + handlers.AddHandler("/viewer/labeledcounters", new TJsonHandler(TJsonLabeledCounters::GetSwagger())); +} + +void InitViewerTenantsJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/tenants", new TJsonHandler(TJsonTenants::GetSwagger())); +} + +void InitViewerHiveStatsJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/hivestats", new TJsonHandler(TJsonHiveStats::GetSwagger())); +} + +void InitViewerTenantInfoJsonHandler(TJsonHandlers &handlers) { + handlers.AddHandler("/viewer/tenantinfo", new TJsonHandler(TJsonTenantInfo::GetSwagger()), 2); +} + +void InitViewerWhoAmIJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/whoami", new TJsonHandler(TJsonWhoAmI::GetSwagger())); +} + +void InitViewerQueryJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/query", new TJsonHandler(TJsonQuery::GetSwagger()), 3); +} + +void InitViewerNetInfoJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/netinfo", new TJsonHandler(TJsonNetInfo::GetSwagger())); +} + +void InitViewerComputeJsonHandler(TJsonHandlers &handlers) { + handlers.AddHandler("/viewer/compute", new TJsonHandler(TJsonCompute::GetSwagger())); +} + +void InitViewerHealthCheckJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/healthcheck", new TJsonHandler(TJsonHealthCheck::GetSwagger())); +} + +void InitViewerNodesJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/nodes", new TJsonHandler(TJsonNodes::GetSwagger()), 9); +} + +void InitViewerACLJsonHandler(TJsonHandlers &jsonHandlers) { + jsonHandlers.AddHandler("/viewer/acl", new TJsonHandler(TJsonACL::GetSwagger())); +} + +void InitViewerGraphJsonHandler(TJsonHandlers &handlers) { + handlers.AddHandler("/viewer/graph", new TJsonHandler(TJsonGraph::GetSwagger())); +} + +void InitViewerRenderJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/render", new TJsonHandler(TJsonRender::GetSwagger())); +} + +void InitViewerAutocompleteJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/autocomplete", new TJsonHandler(TJsonAutocomplete::GetSwagger()), 2); +} + +void InitViewerCheckAccessJsonHandler(TJsonHandlers& jsonHandlers) { + jsonHandlers.AddHandler("/viewer/check_access", new TJsonHandler(TCheckAccess::GetSwagger())); +} + +void InitViewerFeatureFlagsJsonHandler(TJsonHandlers& handlers) { + handlers.AddHandler("/viewer/feature_flags", new TJsonHandler(TJsonFeatureFlags::GetSwagger()), 2); +} + void InitViewerJsonHandlers(TJsonHandlers& jsonHandlers) { - jsonHandlers.AddHandler("/viewer/nodelist", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/nodeinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/sysinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/vdiskinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/pdiskinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/tabletinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/describe", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/describe_topic", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/describe_consumer", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/hotkeys", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/hiveinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/bsgroupinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/bscontrollerinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/config", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/counters", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/topicinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/pqconsumerinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/tabletcounters", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/storage", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/storage_usage", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/metainfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/browse", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/cluster", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/content", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/labeledcounters", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/tenants", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/hivestats", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/tenantinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/whoami", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/query", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/netinfo", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/compute", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/healthcheck", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/nodes", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/acl", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/graph", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/render", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/autocomplete", new TJsonHandler); - jsonHandlers.AddHandler("/viewer/check_access", new TJsonHandler); + InitViewerCapabilitiesJsonHandler(jsonHandlers); + InitViewerNodelistJsonHandler(jsonHandlers); + InitViewerNodeInfoJsonHandler(jsonHandlers); + InitViewerSysInfoJsonHandler(jsonHandlers); + InitViewerVDiskInfoJsonHandler(jsonHandlers); + InitViewerPDiskInfoJsonHandler(jsonHandlers); + InitViewerTabletInfoJsonHandler(jsonHandlers); + InitViewerDescribeJsonHandler(jsonHandlers); + InitViewerDescribeTopicJsonHandler(jsonHandlers); + InitViewerDescribeConsumerJsonHandler(jsonHandlers); + InitViewerHotkeysJsonHandler(jsonHandlers); + InitViewerHiveInfoJsonHandler(jsonHandlers); + InitViewerBSGroupInfoJsonHandler(jsonHandlers); + InitViewerBSControllerInfoJsonHandler(jsonHandlers); + InitViewerConfigJsonHandler(jsonHandlers); + InitViewerCountersJsonHandler(jsonHandlers); + InitViewerTopicInfoJsonHandler(jsonHandlers); + InitViewerPQConsumerInfoJsonHandler(jsonHandlers); + InitViewerTabletCountersJsonHandler(jsonHandlers); + InitViewerStorageJsonHandler(jsonHandlers); + InitViewerStorageUsageJsonHandler(jsonHandlers); + InitViewerClusterJsonHandler(jsonHandlers); + InitViewerLabeledCountersJsonHandler(jsonHandlers); + InitViewerTenantsJsonHandler(jsonHandlers); + InitViewerHiveStatsJsonHandler(jsonHandlers); + InitViewerTenantInfoJsonHandler(jsonHandlers); + InitViewerWhoAmIJsonHandler(jsonHandlers); + InitViewerQueryJsonHandler(jsonHandlers); + InitViewerNetInfoJsonHandler(jsonHandlers); + InitViewerComputeJsonHandler(jsonHandlers); + InitViewerHealthCheckJsonHandler(jsonHandlers); + InitViewerNodesJsonHandler(jsonHandlers); + InitViewerACLJsonHandler(jsonHandlers); + InitViewerGraphJsonHandler(jsonHandlers); + InitViewerRenderJsonHandler(jsonHandlers); + InitViewerAutocompleteJsonHandler(jsonHandlers); + InitViewerCheckAccessJsonHandler(jsonHandlers); + InitViewerFeatureFlagsJsonHandler(jsonHandlers); } } diff --git a/ydb/core/viewer/json_hiveinfo.h b/ydb/core/viewer/json_hiveinfo.h deleted file mode 100644 index d5f97f8eabe0..000000000000 --- a/ydb/core/viewer/json_hiveinfo.h +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "viewer.h" -#include "json_pipe_req.h" -#include - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; - -class TJsonHiveInfo : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TAutoPtr HiveInfo; - TJsonSettings JsonSettings; - ui32 Timeout = 0; - TNodeId NodeId = 0; - -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonHiveInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) - {} - - void Bootstrap() { - const auto& params(Event->Get()->Request.GetParams()); - ui64 hiveId = FromStringWithDefault(params.Get("hive_id"), 0); - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - NodeId = FromStringWithDefault(params.Get("node"), 0); - InitConfig(params); - if (hiveId != 0 ) { - TAutoPtr request = new TEvHive::TEvRequestHiveInfo(); - if (params.Has("tablet_id")) { - request->Record.SetTabletID(FromStringWithDefault(params.Get("tablet_id"), 0)); - } - if (params.Has("tablet_type")) { - request->Record.SetTabletType(static_cast(FromStringWithDefault(params.Get("tablet_type"), 0))); - } - if (FromStringWithDefault(params.Get("followers"), false)) { - request->Record.SetReturnFollowers(true); - } - if (FromStringWithDefault(params.Get("metrics"), false)) { - request->Record.SetReturnMetrics(true); - } - SendRequestToPipe(ConnectTabletPipe(hiveId), request.Release()); - Become(&TThis::StateRequestedInfo, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } else { - ReplyAndPassAway(); - } - } - - STATEFN(StateRequestedInfo) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvHive::TEvResponseHiveInfo, Handle); - hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - void Handle(TEvHive::TEvResponseHiveInfo::TPtr& ev) { - HiveInfo = ev->Release(); - RequestDone(); - } - - void ReplyAndPassAway() { - TStringStream json; - if (HiveInfo != nullptr) { - if (NodeId != 0) { - for (auto itRecord = HiveInfo->Record.MutableTablets()->begin(); itRecord != HiveInfo->Record.MutableTablets()->end();) { - if (itRecord->GetNodeID() != NodeId) { - itRecord = HiveInfo->Record.MutableTablets()->erase(itRecord); - } else { - ++itRecord; - } - } - } - TProtoToJson::ProtoToJson(json, HiveInfo->Record, JsonSettings); - } else { - json << "null"; - } - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } - - void HandleTimeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: hive_id - in: query - description: hive identifier (tablet id) - required: true - type: string - - name: tablet_id - in: query - description: tablet id filter - required: false - type: string - - name: tablet_type - in: query - description: tablet type filter - required: false - type: string - - name: followers - in: query - description: return followers - required: false - type: boolean - - name: metrics - in: query - description: return tablet metrics - required: false - type: boolean - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Hive information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about tablets from Hive"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_local_rpc.h b/ydb/core/viewer/json_local_rpc.h index b81969e25d20..df1ceafd792f 100644 --- a/ydb/core/viewer/json_local_rpc.h +++ b/ydb/core/viewer/json_local_rpc.h @@ -1,20 +1,9 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" - #include -#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { struct TEvLocalRpcPrivate { enum EEv { @@ -35,27 +24,16 @@ struct TEvLocalRpcPrivate { }; }; -using namespace NActors; -using NSchemeShard::TEvSchemeShard; - template -class TJsonLocalRpc : public TActorBootstrapped> { +class TJsonLocalRpc : public TViewerPipeClient { using TThis = TJsonLocalRpc; - using TBase = TActorBootstrapped; - - using TBase::Send; - using TBase::PassAway; - using TBase::Become; + using TBase = TViewerPipeClient; protected: - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; - TProtoRequest Request; + using TBase::ReplyAndPassAway; + using TRequestProtoType = TProtoRequest; + std::vector AllowedMethods = {}; TAutoPtr> Result; - - TJsonSettings JsonSettings; - ui32 Timeout = 0; - TString Database; NThreading::TFuture RpcFuture; public: @@ -63,13 +41,11 @@ class TJsonLocalRpc : public TActorBootstrappedname()) {} - TProtoRequest Params2Proto(const TCgiParameters& params) { - TProtoRequest request; + void Params2Proto(const TCgiParameters& params, TRequestProtoType& request) { using google::protobuf::Descriptor; using google::protobuf::Reflection; using google::protobuf::FieldDescriptor; @@ -119,44 +95,52 @@ class TJsonLocalRpc : public TActorBootstrappedGet()->Request.GetPostContent(); - if (!postData.empty()) { - try { - NProtobufJson::Json2Proto(postData, request, json2ProtoConfig); - } - catch (const yexception& e) { - ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", e.what())); + virtual bool ValidateRequest(TRequestProtoType& request) { + using google::protobuf::Descriptor; + using google::protobuf::Reflection; + using google::protobuf::FieldDescriptor; + const Descriptor& descriptor = *TRequestProtoType::GetDescriptor(); + const Reflection& reflection = *TRequestProtoType::GetReflection(); + for (int idx = 0; idx < descriptor.field_count(); ++idx) { + const FieldDescriptor* field = descriptor.field(idx); + const auto& options(field->options()); + if (options.HasExtension(Ydb::required)) { + if (options.GetExtension(Ydb::required)) { + if (!reflection.HasField(request, field)) { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", TStringBuilder() << "field '" << field->name() << "' is required")); + return false; + } + } } - } else { - const auto& params(Event->Get()->Request.GetParams()); - return Params2Proto(params); } - return request; + return true; } - bool PostToRequest() { + bool Params2Proto(TRequestProtoType& request) { auto postData = Event->Get()->Request.GetPostContent(); if (!postData.empty()) { try { - NProtobufJson::Json2Proto(postData, Request, {}); - return true; + NProtobufJson::Json2Proto(postData, request); } catch (const yexception& e) { - ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", e.what())); + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", e.what())); return false; } + } else { + const auto& params(Event->Get()->Request.GetParams()); + Params2Proto(params, request); + } + if (!ValidateRequest(request)) { + return false; } return true; } - void SendGrpcRequest() { - RpcFuture = NRpcService::DoLocalRpc(std::move(Request), Database, Event->Get()->UserToken, TlsActivationContext->ActorSystem()); + void SendGrpcRequest(TRequestProtoType&& request) { + // TODO(xenoxeno): pass trace id + RpcFuture = NRpcService::DoLocalRpc(std::move(request), Database, Event->Get()->UserToken, TlsActivationContext->ActorSystem()); RpcFuture.Subscribe([actorId = TBase::SelfId(), actorSystem = TlsActivationContext->ActorSystem()] (const NThreading::TFuture& future) { auto& response = future.GetValueSync(); @@ -182,14 +166,21 @@ class TJsonLocalRpc : public TActorBootstrappedGet()->Request.GetParams()); - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), true); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - - SendGrpcRequest(); - - Become(&TThis::StateRequested, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + if (!AllowedMethods.empty() && std::find(AllowedMethods.begin(), AllowedMethods.end(), Event->Get()->Request.GetMethod()) == AllowedMethods.end()) { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "Method is not allowed")); + } + if (Database.empty()) { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "field 'database' is required")); + } + if (TBase::NeedToRedirect()) { + return; + } + TRequestProtoType request; + if (!Params2Proto(request)) { + return; + } + SendGrpcRequest(std::move(request)); + Become(&TThis::StateRequested, Timeout, new TEvents::TEvWakeup()); } void Handle(typename TEvLocalRpcPrivate::TEvGrpcRequestResult::TPtr& ev) { @@ -206,38 +197,24 @@ class TJsonLocalRpc : public TActorBootstrappedStatus) { - if (!Result->Status->IsSuccess()) { + if (Result->Status->IsSuccess()) { + return ReplyAndPassAway(GetHTTPOKJSON(Result->Message)); + } else { NJson::TJsonValue json; TString message; MakeJsonErrorReply(json, message, Result->Status.value()); TStringStream stream; NJson::WriteJson(&stream, &json); if (Result->Status->GetStatus() == NYdb::EStatus::UNAUTHORIZED) { - return ReplyAndPassAway(Viewer->GetHTTPFORBIDDEN(Event->Get(), "application/json", stream.Str())); + return ReplyAndPassAway(GetHTTPFORBIDDEN("application/json", stream.Str()), message); } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "application/json", stream.Str())); + return ReplyAndPassAway(GetHTTPBADREQUEST("application/json", stream.Str()), message); } - } else { - TStringStream json; - TProtoToJson::ProtoToJson(json, Result->Message, JsonSettings); - return ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), json.Str())); } } else { - return ReplyAndPassAway(Viewer->GetHTTPINTERNALERROR(Event->Get())); + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "no Result or Status"), "internal error"); } } - - - void HandleTimeout() { - ReplyAndPassAway(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get())); - } - - void ReplyAndPassAway(TString data) { - Send(Event->Sender, new NMon::TEvHttpInfoRes(data, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } }; - -} -} +} // namespace NKikimr::NViewer diff --git a/ydb/core/viewer/json_nodes.h b/ydb/core/viewer/json_nodes.h deleted file mode 100644 index 34f9288e08e9..000000000000 --- a/ydb/core/viewer/json_nodes.h +++ /dev/null @@ -1,1033 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" -#include "viewer_helper.h" -#include "json_pipe_req.h" -#include "json_sysinfo.h" -#include "json_pdiskinfo.h" - -namespace NKikimr { -namespace NViewer { - -using namespace NActors; -using namespace NNodeWhiteboard; -using ::google::protobuf::FieldDescriptor; - -class TJsonNodes : public TViewerPipeClient { - using TThis = TJsonNodes; - using TBase = TViewerPipeClient; - using TNodeId = ui32; - using TPDiskId = std::pair; - IViewer* Viewer; - TActorId Initiator; - NMon::TEvHttpInfo::TPtr Event; - std::unique_ptr NodesInfo; - std::unordered_map PDiskInfo; - std::unordered_map VDiskInfo; - std::unordered_map> TabletInfo; - std::unordered_map SysInfo; - std::unordered_map NavigateResult; - std::unique_ptr BaseConfig; - std::unordered_map BaseConfigGroupIndex; - std::unordered_map DisconnectTime; - std::unordered_map NodeName; - TJsonSettings JsonSettings; - ui32 Timeout = 0; - TString FilterTenant; - TSubDomainKey FilterSubDomainKey; - TString FilterPath; - TString FilterStoragePool; - std::unordered_set FilterNodeIds; - std::unordered_set FilterGroupIds; - std::unordered_set PassedNodeIds; - std::vector NodeIds; - std::optional Offset; - std::optional Limit; - ui32 UptimeSeconds = 0; - bool ProblemNodesOnly = false; - TString Filter; - - enum class EWith { - Everything, - MissingDisks, - SpaceProblems, - }; - EWith With = EWith::Everything; - - enum class EType { - Any, - Static, - Dynamic, - }; - EType Type = EType::Any; - - enum class ESort { - NodeId, - Host, - DC, - Rack, - Version, - Uptime, - Memory, - CPU, - LoadAverage, - Missing, - }; - ESort Sort = ESort::NodeId; - bool ReverseSort = false; - bool SortedNodeList = false; - bool LimitApplied = false; - - bool Storage = false; - bool Tablets = false; - TPathId FilterPathId; - bool ResolveGroupsToNodes = false; - TNodeId MinAllowedNodeId = std::numeric_limits::min(); - TNodeId MaxAllowedNodeId = std::numeric_limits::max(); - ui32 RequestsBeforeNodeList = 0; - ui64 HiveId = 0; - std::optional MaximumDisksPerNode; - -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TString GetLogPrefix() { - static TString prefix = "json/nodes "; - return prefix; - } - - TJsonNodes(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Initiator(ev->Sender) - , Event(std::move(ev)) - { - const auto& params(Event->Get()->Request.GetParams()); - JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); - JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); - InitConfig(params); - Timeout = FromStringWithDefault(params.Get("timeout"), 10000); - UptimeSeconds = FromStringWithDefault(params.Get("uptime"), 0); - ProblemNodesOnly = FromStringWithDefault(params.Get("problems_only"), ProblemNodesOnly); - Filter = params.Get("filter"); - FilterPath = params.Get("path"); - FilterTenant = params.Get("tenant"); - FilterStoragePool = params.Get("pool"); - SplitIds(params.Get("node_id"), ',', FilterNodeIds); - auto itZero = FilterNodeIds.find(0); - if (itZero != FilterNodeIds.end()) { - FilterNodeIds.erase(itZero); - FilterNodeIds.insert(TlsActivationContext->ActorSystem()->NodeId); - } - if (params.Get("with") == "missing") { - With = EWith::MissingDisks; - } else if (params.Get("with") == "space") { - With = EWith::SpaceProblems; - } - if (params.Has("offset")) { - Offset = FromStringWithDefault(params.Get("offset"), 0); - } - if (params.Has("limit")) { - Limit = FromStringWithDefault(params.Get("limit"), std::numeric_limits::max()); - } - if (params.Get("type") == "static") { - Type = EType::Static; - } else if (params.Get("type") == "dynamic") { - Type = EType::Dynamic; - } else if (params.Get("type") == "any") { - Type = EType::Any; - } - Storage = FromStringWithDefault(params.Get("storage"), Storage); - Tablets = FromStringWithDefault(params.Get("tablets"), Tablets); - ResolveGroupsToNodes = FromStringWithDefault(params.Get("resolve_groups"), ResolveGroupsToNodes); - TStringBuf sort = params.Get("sort"); - if (sort) { - if (sort.StartsWith("-") || sort.StartsWith("+")) { - ReverseSort = (sort[0] == '-'); - sort.Skip(1); - } - if (sort == "NodeId") { - Sort = ESort::NodeId; - } else if (sort == "Host") { - Sort = ESort::Host; - } else if (sort == "DC") { - Sort = ESort::DC; - } else if (sort == "Rack") { - Sort = ESort::Rack; - } else if (sort == "Version") { - Sort = ESort::Version; - } else if (sort == "Uptime") { - Sort = ESort::Uptime; - } else if (sort == "Memory") { - Sort = ESort::Memory; - } else if (sort == "CPU") { - Sort = ESort::CPU; - } else if (sort == "LoadAverage") { - Sort = ESort::LoadAverage; - } else if (sort == "Missing") { - Sort = ESort::Missing; - } - } - } - - void Bootstrap() { - BLOG_TRACE("Bootstrap()"); - if (Type != EType::Any) { - TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; - if (dynamicNameserviceConfig) { - if (Type == EType::Static) { - MaxAllowedNodeId = dynamicNameserviceConfig->MaxStaticNodeId; - } - if (Type == EType::Dynamic) { - MinAllowedNodeId = dynamicNameserviceConfig->MaxStaticNodeId + 1; - } - } - } - - if (Storage) { - BLOG_TRACE("RequestBSControllerConfig()"); - RequestBSControllerConfig(); - ++RequestsBeforeNodeList; - } - - if (!FilterTenant.empty()) { - RequestForTenant(FilterTenant); - } - - if (!FilterPath.empty()) { - BLOG_TRACE("Requesting navigate for " << FilterPath); - RequestSchemeCacheNavigate(FilterPath); - ++RequestsBeforeNodeList; - } - - BLOG_TRACE("Request TEvListNodes"); - SendRequest(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); - ++RequestsBeforeNodeList; - if (Requests == 0) { - ReplyAndPassAway(); - return; - } - TBase::Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); - } - - void PassAway() override { - BLOG_TRACE("PassAway()"); - for (const TNodeId nodeId : NodeIds) { - Send(TActivationContext::InterconnectProxy(nodeId), new TEvents::TEvUnsubscribe()); - } - TBase::PassAway(); - } - - void RequestForTenant(const TString& filterTenant) { - BLOG_TRACE("RequestForTenant " << filterTenant); - FilterTenant = filterTenant; - if (Type == EType::Static || Type == EType::Any) { - if (ResolveGroupsToNodes) { - if (!Storage) { - BLOG_TRACE("RequestBSControllerConfig()"); - RequestBSControllerConfig(); - ++RequestsBeforeNodeList; - } - } - } - if (Type == EType::Dynamic || Type == EType::Any) { - BLOG_TRACE("RequestStateStorageEndpointsLookup for " << FilterTenant); - RequestStateStorageEndpointsLookup(FilterTenant); // to get dynamic nodes - ++RequestsBeforeNodeList; - } - } - - bool CheckNodeFilters(TNodeId nodeId) { - if (Storage && With == EWith::MissingDisks) { - auto itPDiskState = PDiskInfo.find(nodeId); - if (itPDiskState != PDiskInfo.end()) { - int disksNormal = 0; - for (const auto& protoPDiskInfo : itPDiskState->second.GetPDiskStateInfo()) { - if (protoPDiskInfo.state() == NKikimrBlobStorage::TPDiskState::Normal) { - ++disksNormal; - } - } - if (itPDiskState->second.pdiskstateinfo_size() == disksNormal) { - return false; - } - } - } - auto itSysInfo = SysInfo.find(nodeId); - if (itSysInfo != SysInfo.end() && itSysInfo->second.SystemStateInfoSize() > 0) { - const auto& sysState(itSysInfo->second.GetSystemStateInfo(0)); - if (Storage && With == EWith::SpaceProblems) { - if (!sysState.HasMaxDiskUsage() || sysState.GetMaxDiskUsage() < 0.85) { - return false; - } - } - if (UptimeSeconds > 0 && sysState.HasStartTime() && itSysInfo->second.HasResponseTime() - && itSysInfo->second.GetResponseTime() - sysState.GetStartTime() > UptimeSeconds * 1000) { - return false; - } - if (ProblemNodesOnly && sysState.HasSystemState() - && GetViewerFlag(sysState.GetSystemState()) == NKikimrViewer::EFlag::Green) { - return false; - } - if (Filter) { - if (sysState.HasHost() && sysState.GetHost().Contains(Filter)) { - return true; - } - if (std::to_string(nodeId).contains(Filter)) { - return true; - } - return false; - } - } else { - if (Storage && With == EWith::SpaceProblems) { - return false; - } - if (UptimeSeconds > 0) { - return false; - } - if (ProblemNodesOnly) { - return false; - } - if (Filter) { - return false; - } - } - - return true; - } - - bool HasNodeFilter() { - return With != EWith::Everything || UptimeSeconds != 0 || ProblemNodesOnly || !Filter.empty(); - } - - void SendNodeRequest(TNodeId nodeId) { - if (PassedNodeIds.insert(nodeId).second) { - if (SortedNodeList) { - // optimization for early paging with default sort - LimitApplied = true; - if (Offset.has_value()) { - if (PassedNodeIds.size() <= Offset.value()) { - return; - } - } - if (Limit.has_value()) { - if (NodeIds.size() >= Limit.value()) { - return; - } - } - } - NodeIds.push_back(nodeId); // order is important - TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); - BLOG_TRACE("SendSystemStateRequest to " << nodeId); - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvSystemStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); - if (Storage) { - BLOG_TRACE("SendV/PDiskStateRequest to " << nodeId); - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvPDiskStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); - SendRequest(whiteboardServiceId, new TEvWhiteboard::TEvVDiskStateRequest(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); - } - if (Tablets && FilterPathId == TPathId()) { - BLOG_TRACE("SendTabletStateRequest to " << nodeId); - auto request = std::make_unique(); - request->Record.SetGroupBy("Type,State"); - SendRequest(whiteboardServiceId, request.release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); - } - } - } - - void ProcessNodeIds() { - BLOG_TRACE("ProcessNodeIds()"); - - if (!HasNodeFilter()) { - switch (Sort) { - case ESort::NodeId: { - SortCollection(NodesInfo->Nodes, [](const TEvInterconnect::TNodeInfo& node) { return node.NodeId;}, ReverseSort); - SortedNodeList = true; - break; - } - case ESort::Host: { - SortCollection(NodesInfo->Nodes, [](const TEvInterconnect::TNodeInfo& node) { return node.Host;}, ReverseSort); - SortedNodeList = true; - break; - } - case ESort::DC: { - SortCollection(NodesInfo->Nodes, [](const TEvInterconnect::TNodeInfo& node) { return node.Location.GetDataCenterId();}, ReverseSort); - SortedNodeList = true; - break; - } - default: - break; - } - } - - for (const auto& ni : NodesInfo->Nodes) { - if ((FilterNodeIds.empty() || FilterNodeIds.count(ni.NodeId) > 0) && ni.NodeId >= MinAllowedNodeId && ni.NodeId <= MaxAllowedNodeId) { - SendNodeRequest(ni.NodeId); - } - } - } - - void Handle(TEvBlobStorage::TEvControllerConfigResponse::TPtr& ev) { - BLOG_TRACE("Received TEvControllerConfigResponse"); - const NKikimrBlobStorage::TEvControllerConfigResponse& pbRecord(ev->Get()->Record); - if (pbRecord.HasResponse() && pbRecord.GetResponse().StatusSize() > 0) { - const NKikimrBlobStorage::TConfigResponse::TStatus& pbStatus(pbRecord.GetResponse().GetStatus(0)); - if (pbStatus.HasBaseConfig()) { - BaseConfig.reset(ev->Release().Release()); - const NKikimrBlobStorage::TEvControllerConfigResponse& pbRecord(BaseConfig->Record); - const NKikimrBlobStorage::TConfigResponse::TStatus& pbStatus(pbRecord.GetResponse().GetStatus(0)); - const NKikimrBlobStorage::TBaseConfig& pbConfig(pbStatus.GetBaseConfig()); - for (const NKikimrBlobStorage::TBaseConfig::TGroup& group : pbConfig.GetGroup()) { - BaseConfigGroupIndex[group.GetGroupId()] = &group; - } - std::unordered_map disksPerNode; - disksPerNode.reserve(pbConfig.NodeSize()); - for (const NKikimrBlobStorage::TBaseConfig::TPDisk& pdisk : pbConfig.GetPDisk()) { - disksPerNode[pdisk.GetNodeId()]++; - } - int maximumDisksPerNode = 0; - for (const auto& [nodeId, disks] : disksPerNode) { - if (disks > maximumDisksPerNode) { - maximumDisksPerNode = disks; - } - } - MaximumDisksPerNode = maximumDisksPerNode; - } - } - if (ResolveGroupsToNodes) { - BLOG_TRACE("Requesting navigate for " << FilterTenant); - RequestSchemeCacheNavigate(FilterTenant); // to get storage pools and then groups and then pdisks - ++RequestsBeforeNodeList; - } - if (--RequestsBeforeNodeList == 0) { - ProcessNodeIds(); - } - - RequestDone(); - } - - bool IsSubDomainPath(const TSchemeCacheNavigate::TEntry& entry) { - switch (entry.Kind) { - case TSchemeCacheNavigate::EKind::KindSubdomain: - case TSchemeCacheNavigate::EKind::KindExtSubdomain: - return true; - case TSchemeCacheNavigate::EKind::KindPath: - return entry.Self->Info.GetPathId() == NSchemeShard::RootPathId; - default: - return false; - } - } - - void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { - if (ev->Get()->Request->ResultSet.size() == 1 && ev->Get()->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { - TSchemeCacheNavigate::TEntry& entry(ev->Get()->Request->ResultSet.front()); - TString path = CanonizePath(entry.Path); - BLOG_TRACE("Received navigate for " << path); - if (IsSubDomainPath(entry)) { - 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 (FilterTenant.empty()) { - RequestForTenant(path); - } - - if (entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { - TPathId resourceDomainKey(entry.DomainInfo->ResourcesDomainKey); - BLOG_TRACE("Requesting navigate for resource domain " << resourceDomainKey); - RequestSchemeCacheNavigate(resourceDomainKey); - ++RequestsBeforeNodeList; - } else if (Storage && entry.DomainDescription) { - for (const auto& storagePool : entry.DomainDescription->Description.GetStoragePools()) { - TString storagePoolName = storagePool.GetName(); - THolder request = MakeHolder(); - request->Record.SetReturnAllMatchingGroups(true); - request->Record.AddGroupParameters()->MutableStoragePoolSpecifier()->SetName(storagePoolName); - BLOG_TRACE("Requesting BSControllerSelectGroups for " << storagePoolName); - RequestBSControllerSelectGroups(std::move(request)); - ++RequestsBeforeNodeList; - } - } - } else { - if (entry.DomainInfo) { - TPathId domainKey(entry.DomainInfo->DomainKey); - BLOG_TRACE("Requesting navigate for parent domain " << domainKey); - RequestSchemeCacheNavigate(domainKey); - ++RequestsBeforeNodeList; - - if (!FilterPath.empty() && Tablets && FilterPathId == TPathId()) { - FilterPathId = TPathId(entry.Self->Info.GetSchemeshardId(), entry.Self->Info.GetPathId()); - } - } - } - NavigateResult.emplace(path, std::move(entry)); - - if (HiveId != 0) { - BLOG_TRACE("Requesting hive " << HiveId << " for path id " << FilterPathId); - RequestHiveNodeStats(HiveId, FilterPathId); - ++RequestsBeforeNodeList; - } - } else { - BLOG_TRACE("Error receiving Navigate response"); - FilterNodeIds = { 0 }; - } - - if (--RequestsBeforeNodeList == 0) { - ProcessNodeIds(); - } - RequestDone(); - } - - 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()) { - tabletInfo.emplace_back(); - NKikimrViewer::TTabletStateInfo& viewerTablet(tabletInfo.back()); - viewerTablet.SetType(NKikimrTabletBase::TTabletTypes::EType_Name(stateStats.GetTabletType())); - viewerTablet.SetCount(stateStats.GetCount()); - viewerTablet.SetState(GetFlagFromTabletState(stateStats.GetVolatileState())); - } - BLOG_TRACE("HiveNodeStats filter node by " << nodeId); - FilterNodeIds.insert(nodeId); - DisconnectTime[nodeId] = nodeStats.GetLastAliveTimestamp(); - if (nodeStats.HasNodeName()) { - NodeName[nodeId] = nodeStats.GetNodeName(); - } - } - if (--RequestsBeforeNodeList == 0) { - ProcessNodeIds(); - } - RequestDone(); - } - - void Handle(TEvBlobStorage::TEvControllerSelectGroupsResult::TPtr& ev) { - BLOG_TRACE("Received TEvControllerSelectGroupsResult"); - for (const auto& matchingGroups : ev->Get()->Record.GetMatchingGroups()) { - for (const auto& group : matchingGroups.GetGroups()) { - TString storagePoolName = group.GetStoragePoolName(); - if (FilterStoragePool.empty() || FilterStoragePool == storagePoolName) { - if (FilterGroupIds.emplace(group.GetGroupID()).second && BaseConfig) { - auto itBaseConfigGroupIndex = BaseConfigGroupIndex.find(group.GetGroupID()); - if (itBaseConfigGroupIndex != BaseConfigGroupIndex.end()) { - for (const NKikimrBlobStorage::TVSlotId& vslot : itBaseConfigGroupIndex->second->GetVSlotId()) { - BLOG_TRACE("SelectGroups filter by node " << vslot.GetNodeId()); - FilterNodeIds.insert(vslot.GetNodeId()); - } - } - } - } - } - } - if (--RequestsBeforeNodeList == 0) { - ProcessNodeIds(); - } - RequestDone(); - } - - void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { - BLOG_TRACE("Received TEvNodesInfo " << ev->Get()->Nodes.size()); - NodesInfo.reset(ev->Release().Release()); - if (--RequestsBeforeNodeList == 0) { - ProcessNodeIds(); - } - RequestDone(); - } - - void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - 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(); - } - RequestDone(); - } - - void Undelivered(TEvents::TEvUndelivered::TPtr& ev) { - ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Undelivered type " << ev->Get()->SourceType << " from node " << nodeId); - switch (ev->Get()->SourceType) { - case TEvWhiteboard::EvSystemStateRequest: - if (SysInfo.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvPDiskStateRequest: - if (PDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvPDiskStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvVDiskStateRequest: - if (VDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvVDiskStateResponse{}).second) { - RequestDone(); - } - break; - case TEvWhiteboard::EvTabletStateRequest: - RequestDone(); - break; - } - } - - void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { - ui32 nodeId = ev->Get()->NodeId; - BLOG_TRACE("Disconnected from node " << nodeId); - if (SysInfo.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - if (Storage) { - if (PDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvPDiskStateResponse{}).second) { - RequestDone(); - } - if (VDiskInfo.emplace(nodeId, NKikimrWhiteboard::TEvVDiskStateResponse{}).second) { - RequestDone(); - } - } - if (Tablets) { - if (TabletInfo.emplace(nodeId, std::vector()).second) { - RequestDone(); - } - } - } - - void Handle(TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - BLOG_TRACE("SystemStateResponse from node " << nodeId); - SysInfo[nodeId] = std::move(ev->Get()->Record); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvPDiskStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - BLOG_TRACE("PDiskStateResponse from node " << nodeId); - PDiskInfo[nodeId] = std::move(ev->Get()->Record); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvVDiskStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - BLOG_TRACE("VDiskStateResponse from node " << nodeId); - VDiskInfo[nodeId] = std::move(ev->Get()->Record); - RequestDone(); - } - - void Handle(TEvWhiteboard::TEvTabletStateResponse::TPtr& ev) { - ui64 nodeId = ev.Get()->Cookie; - BLOG_TRACE("TabletStateResponse from node " << nodeId); - NKikimrWhiteboard::TEvTabletStateResponse response = std::move(ev->Get()->Record); - bool needToGroup = response.TabletStateInfoSize() > 0 && !response.GetTabletStateInfo(0).HasCount(); - if (needToGroup) { // for compatibility with older versions - GroupWhiteboardResponses(response, "Type,Overall", false); - } - auto& vecTablets(TabletInfo[nodeId]); - for (const NKikimrWhiteboard::TTabletStateInfo& tablet : response.GetTabletStateInfo()) { - if (tablet.GetState() == NKikimrWhiteboard::TTabletStateInfo::Dead - || tablet.GetState() == NKikimrWhiteboard::TTabletStateInfo::Deleted) { - continue; - } - vecTablets.emplace_back(); - NKikimrViewer::TTabletStateInfo& viewerTablet(vecTablets.back()); - viewerTablet.SetType(NKikimrTabletBase::TTabletTypes::EType_Name(tablet.GetType())); - viewerTablet.SetCount(tablet.GetCount()); - viewerTablet.SetState(GetFlagFromTabletState(tablet.GetState())); - } - RequestDone(); - } - - STATEFN(StateWork) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvInterconnect::TEvNodesInfo, Handle); - hFunc(TEvWhiteboard::TEvSystemStateResponse, Handle); - hFunc(TEvWhiteboard::TEvPDiskStateResponse, Handle); - hFunc(TEvWhiteboard::TEvVDiskStateResponse, Handle); - hFunc(TEvWhiteboard::TEvTabletStateResponse, Handle); - hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); - hFunc(TEvHive::TEvResponseHiveNodeStats, Handle); - hFunc(TEvBlobStorage::TEvControllerSelectGroupsResult, Handle); - hFunc(TEvBlobStorage::TEvControllerConfigResponse, Handle); - hFunc(TEvStateStorage::TEvBoardInfo, Handle); - hFunc(TEvents::TEvUndelivered, Undelivered); - hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); - hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - NKikimrWhiteboard::TPDiskStateInfo& GetPDisk(TPDiskId pDiskId) { - auto itPDiskInfo = PDiskInfo.find(pDiskId.first); - if (itPDiskInfo == PDiskInfo.end()) { - itPDiskInfo = PDiskInfo.insert({pDiskId.first, NKikimrWhiteboard::TEvPDiskStateResponse{}}).first; - } - - for (auto& pDiskInfo : *itPDiskInfo->second.mutable_pdiskstateinfo()) { - if (pDiskInfo.pdiskid() == pDiskId.second) { - return pDiskInfo; - } - } - - NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo = *itPDiskInfo->second.add_pdiskstateinfo(); - pDiskInfo.SetPDiskId(pDiskId.second); - return pDiskInfo; - } - - static double GetCPU(const NKikimrWhiteboard::TSystemStateInfo& sysInfo) { - double cpu = 0; - if (sysInfo.PoolStatsSize() > 0) { - for (const auto& ps : sysInfo.GetPoolStats()) { - cpu = std::max(cpu, ps.GetUsage()); - } - } - return cpu; - } - - static double GetLoadAverage(const NKikimrWhiteboard::TSystemStateInfo& sysInfo) { - if (sysInfo.LoadAverageSize() > 0 && sysInfo.GetNumberOfCpus() > 0) { - return sysInfo.GetLoadAverage(0) * 100 / sysInfo.GetNumberOfCpus(); - } - return 0; - } - - static uint32 GetMissing(const NKikimrViewer::TNodeInfo& nodeInfo) { - uint32 missing = 0; - for (const auto& pDisk : nodeInfo.GetPDisks()) { - if (pDisk.state() != NKikimrBlobStorage::TPDiskState::Normal) { - missing++; - } - } - return missing; - } - - void ReplyAndPassAway() { - NKikimrViewer::TNodesInfo result; - - if (Storage && BaseConfig) { - const NKikimrBlobStorage::TEvControllerConfigResponse& pbRecord(BaseConfig->Record); - const NKikimrBlobStorage::TConfigResponse::TStatus& pbStatus(pbRecord.GetResponse().GetStatus(0)); - const NKikimrBlobStorage::TBaseConfig& pbConfig(pbStatus.GetBaseConfig()); - for (const NKikimrBlobStorage::TBaseConfig::TPDisk& pDisk : pbConfig.GetPDisk()) { - if (!FilterNodeIds.empty() && FilterNodeIds.count(pDisk.GetNodeId()) == 0) { - continue; - } - if (pDisk.GetNodeId() < MinAllowedNodeId || pDisk.GetNodeId() > MaxAllowedNodeId) { - continue; - } - TPDiskId pDiskId(pDisk.GetNodeId(), pDisk.GetPDiskId()); - NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo = GetPDisk(pDiskId); - pDiskInfo.SetPath(pDisk.GetPath()); - pDiskInfo.SetGuid(pDisk.GetGuid()); - pDiskInfo.SetCategory(static_cast(pDisk.GetType())); - if (pDiskInfo.GetTotalSize() == 0) { - pDiskInfo.SetTotalSize(pDisk.GetPDiskMetrics().GetTotalSize()); - } - if (pDiskInfo.GetAvailableSize() == 0) { - pDiskInfo.SetAvailableSize(pDisk.GetPDiskMetrics().GetAvailableSize()); - } - } - for (const NKikimrBlobStorage::TBaseConfig::TNode& node : pbConfig.GetNode()) { - if (!FilterNodeIds.empty() && FilterNodeIds.count(node.GetNodeId()) == 0) { - continue; - } - if (node.GetNodeId() < MinAllowedNodeId || node.GetNodeId() > MaxAllowedNodeId) { - continue; - } - if (node.GetLastDisconnectTimestamp() > node.GetLastConnectTimestamp()) { - DisconnectTime[node.GetNodeId()] = node.GetLastDisconnectTimestamp() / 1000; // us -> ms - } - } - } - - bool noDC = true; - bool noRack = true; - - for (TNodeId nodeId : NodeIds) { - if (!CheckNodeFilters(nodeId)) { - continue; - } - - NKikimrViewer::TNodeInfo& nodeInfo = *result.add_nodes(); - nodeInfo.set_nodeid(nodeId); - BLOG_TRACE("AddingNode " << nodeId); - auto itSystemState = SysInfo.find(nodeId); - if (itSystemState != SysInfo.end() && itSystemState->second.SystemStateInfoSize() > 0) { - *nodeInfo.MutableSystemState() = itSystemState->second.GetSystemStateInfo(0); - } else if (NodesInfo != nullptr) { - auto* icNodeInfo = NodesInfo->GetNodeInfo(nodeId); - if (icNodeInfo != nullptr) { - nodeInfo.MutableSystemState()->SetHost(icNodeInfo->Host); - } - auto itDisconnectTime = DisconnectTime.find(nodeId); - if (itDisconnectTime != DisconnectTime.end()) { - nodeInfo.MutableSystemState()->SetDisconnectTime(itDisconnectTime->second); - } - auto itNodeName = NodeName.find(nodeId); - if (itNodeName != NodeName.end()) { - nodeInfo.MutableSystemState()->SetNodeName(itNodeName->second); - } - } - if (Storage) { - auto itPDiskState = PDiskInfo.find(nodeId); - if (itPDiskState != PDiskInfo.end()) { - for (auto& protoPDiskInfo : *itPDiskState->second.MutablePDiskStateInfo()) { - NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo = *nodeInfo.AddPDisks(); - pDiskInfo = std::move(protoPDiskInfo); - } - } - auto itVDiskState = VDiskInfo.find(nodeId); - if (itVDiskState != VDiskInfo.end()) { - for (auto& protoVDiskInfo : *itVDiskState->second.MutableVDiskStateInfo()) { - NKikimrWhiteboard::TVDiskStateInfo& vDiskInfo = *nodeInfo.AddVDisks(); - vDiskInfo = std::move(protoVDiskInfo); - } - } - } - if (Tablets) { - auto itTabletState = TabletInfo.find(nodeId); - if (itTabletState != TabletInfo.end()) { - for (auto& viewerTabletInfo : itTabletState->second) { - NKikimrViewer::TTabletStateInfo& tabletInfo = *nodeInfo.AddTablets(); - tabletInfo = std::move(viewerTabletInfo); - } - } - } - - if (!nodeInfo.GetSystemState().GetLocation().GetDataCenter().empty()) { - noDC = false; - } - if (nodeInfo.GetSystemState().GetSystemLocation().GetDataCenter() != 0) { - noDC = false; - } - if (!nodeInfo.GetSystemState().GetLocation().GetRack().empty()) { - noRack = false; - } - if (nodeInfo.GetSystemState().GetSystemLocation().GetRack() != 0) { - noRack = false; - } - } - - if (!SortedNodeList) { - switch (Sort) { - case ESort::NodeId: - case ESort::Host: - case ESort::DC: - // already sorted - break; - case ESort::Rack: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return node.GetSystemState().GetRack();}, ReverseSort); - break; - case ESort::Version: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return node.GetSystemState().GetVersion();}, ReverseSort); - break; - case ESort::Uptime: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return node.GetSystemState().GetStartTime();}, ReverseSort); - break; - case ESort::Memory: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return node.GetSystemState().GetMemoryUsed();}, ReverseSort); - break; - case ESort::CPU: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return GetCPU(node.GetSystemState());}, ReverseSort); - break; - case ESort::LoadAverage: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return GetLoadAverage(node.GetSystemState());}, ReverseSort); - break; - case ESort::Missing: - SortCollection(*result.MutableNodes(), [](const NKikimrViewer::TNodeInfo& node) { return GetMissing(node);}, ReverseSort); - break; - } - } - - result.SetTotalNodes(PassedNodeIds.size()); - result.SetFoundNodes(LimitApplied ? PassedNodeIds.size() : result.NodesSize()); - - BLOG_TRACE("Total/Found " << result.GetTotalNodes() << "/" << result.GetFoundNodes()); - - if (!LimitApplied) { - auto& nodes = *result.MutableNodes(); - if (Offset.has_value()) { - BLOG_TRACE("ErasingFromBegining " << Offset.value()); - if (size_t(nodes.size()) > Offset.value()) { - nodes.erase(nodes.begin(), std::next(nodes.begin(), Offset.value())); - } else { - nodes.Clear(); - } - } - if (Limit.has_value()) { - BLOG_TRACE("LimitingWith " << Limit.value()); - if (size_t(nodes.size()) > Limit.value()) { - nodes.erase(std::next(nodes.begin(), Limit.value()), nodes.end()); - } - } - } - - for (NKikimrViewer::TNodeInfo& nodeInfo : *result.MutableNodes()) { - if (Storage) { - { - auto& cont(*nodeInfo.MutablePDisks()); - std::sort(cont.begin(), cont.end(), [](const NKikimrWhiteboard::TPDiskStateInfo& a, const NKikimrWhiteboard::TPDiskStateInfo& b) -> bool { - return a.GetPath() < b.GetPath(); - }); - } - { - auto& cont(*nodeInfo.MutableVDisks()); - std::sort(cont.begin(), cont.end(), [](const NKikimrWhiteboard::TVDiskStateInfo& a, const NKikimrWhiteboard::TVDiskStateInfo& b) -> bool { - return VDiskIDFromVDiskID(a.GetVDiskId()) < VDiskIDFromVDiskID(b.GetVDiskId()); - }); - } - } - if (Tablets) { - { - auto& cont(*nodeInfo.MutableTablets()); - std::sort(cont.begin(), cont.end(), [](const NKikimrViewer::TTabletStateInfo& a, const NKikimrViewer::TTabletStateInfo& b) -> bool { - return a.GetType() < b.GetType(); - }); - } - } - } - - if (MaximumDisksPerNode.has_value()) { - result.SetMaximumDisksPerNode(MaximumDisksPerNode.value()); - } - if (noDC) { - result.SetNoDC(true); - } - if (noRack) { - result.SetNoRack(true); - } - - TStringStream json; - TProtoToJson::ProtoToJson(json, result, JsonSettings); - Send(Initiator, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), std::move(json.Str())), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } - - void HandleTimeout() { - ReplyAndPassAway(); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as numbers - required: false - type: boolean - - name: path - in: query - description: path to schema object - required: false - type: string - - name: with - in: query - description: filter nodes by missing disks or space - required: false - type: string - - name: type - in: query - description: nodes type to get (static,dynamic,any) - required: false - type: string - - name: storage - in: query - description: return storage info - required: false - type: boolean - - name: tablets - in: query - description: return tablets info - required: false - type: boolean - - name: sort - in: query - description: sort by (NodeId,Host,DC,Rack,Version,Uptime,Memory,CPU,LoadAverage,Missing) - required: false - type: string - - name: offset - in: query - description: skip N nodes - required: false - type: integer - - name: limit - in: query - description: limit to N nodes - required: false - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: uptime - in: query - description: return only nodes with less uptime in sec. - required: false - type: integer - - name: problems_only - in: query - description: return only problem nodes - required: false - type: boolean - - name: filter - in: query - description: filter nodes by id or host - required: false - type: string - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Nodes info"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Information about nodes"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_pipe_req.cpp b/ydb/core/viewer/json_pipe_req.cpp new file mode 100644 index 000000000000..0425f9a4330d --- /dev/null +++ b/ydb/core/viewer/json_pipe_req.cpp @@ -0,0 +1,806 @@ +#include "json_pipe_req.h" +#include + +namespace NKikimr::NViewer { + +NTabletPipe::TClientConfig TViewerPipeClient::GetPipeClientConfig() { + NTabletPipe::TClientConfig clientConfig; + if (WithRetry) { + clientConfig.RetryPolicy = {.RetryLimitCount = 3}; + } + return clientConfig; +} + +TViewerPipeClient::~TViewerPipeClient() = default; + +TViewerPipeClient::TViewerPipeClient() = default; + +TViewerPipeClient::TViewerPipeClient(NWilson::TTraceId traceId) { + if (traceId) { + Span = {TComponentTracingLevels::THttp::TopLevel, std::move(traceId), "viewer", NWilson::EFlags::AUTO_END}; + } +} + +TViewerPipeClient::TViewerPipeClient(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev, const TString& handlerName) + : Viewer(viewer) + , Event(ev) +{ + InitConfig(Event->Get()->Request.GetParams()); + NWilson::TTraceId traceId; + TStringBuf traceparent = Event->Get()->Request.GetHeader("traceparent"); + if (traceparent) { + traceId = NWilson::TTraceId::FromTraceparentHeader(traceparent, TComponentTracingLevels::ProductionVerbose); + } + TStringBuf wantTrace = Event->Get()->Request.GetHeader("X-Want-Trace"); + TStringBuf traceVerbosity = Event->Get()->Request.GetHeader("X-Trace-Verbosity"); + TStringBuf traceTTL = Event->Get()->Request.GetHeader("X-Trace-TTL"); + if (!traceId && (FromStringWithDefault(wantTrace) || !traceVerbosity.empty() || !traceTTL.empty())) { + ui8 verbosity = TComponentTracingLevels::ProductionVerbose; + if (traceVerbosity) { + verbosity = FromStringWithDefault(traceVerbosity, verbosity); + verbosity = std::min(verbosity, NWilson::TTraceId::MAX_VERBOSITY); + } + ui32 ttl = Max(); + if (traceTTL) { + ttl = FromStringWithDefault(traceTTL, ttl); + ttl = std::min(ttl, NWilson::TTraceId::MAX_TIME_TO_LIVE); + } + traceId = NWilson::TTraceId::NewTraceId(verbosity, ttl); + } + if (traceId) { + Span = {TComponentTracingLevels::THttp::TopLevel, std::move(traceId), handlerName ? "http " + handlerName : "http viewer", NWilson::EFlags::AUTO_END}; + Span.Attribute("request_type", TString(Event->Get()->Request.GetUri().Before('?'))); + Span.Attribute("request_params", TString(Event->Get()->Request.GetUri().After('?'))); + } +} + +TActorId TViewerPipeClient::ConnectTabletPipe(NNodeWhiteboard::TTabletId tabletId) { + TPipeInfo& pipeInfo = PipeInfo[tabletId]; + if (!pipeInfo.PipeClient) { + auto pipe = NTabletPipe::CreateClient(SelfId(), tabletId, GetPipeClientConfig()); + pipeInfo.PipeClient = RegisterWithSameMailbox(pipe); + } + pipeInfo.Requests++; + return pipeInfo.PipeClient; +} + +void TViewerPipeClient::SendEvent(std::unique_ptr event) { + if (DelayedRequests.empty() && Requests < MaxRequestsInFlight) { + TActivationContext::Send(event.release()); + ++Requests; + } else { + DelayedRequests.push_back({ + .Event = std::move(event), + }); + } +} + +void TViewerPipeClient::SendRequest(TActorId recipient, IEventBase* ev, ui32 flags, ui64 cookie, NWilson::TTraceId traceId) { + SendEvent(std::make_unique(recipient, SelfId(), ev, flags, cookie, nullptr /*forwardOnNondelivery*/, std::move(traceId))); +} + +void TViewerPipeClient::SendRequestToPipe(TActorId pipe, IEventBase* ev, ui64 cookie, NWilson::TTraceId traceId) { + std::unique_ptr event = std::make_unique(pipe, SelfId(), ev, 0 /*flags*/, cookie, nullptr /*forwardOnNondelivery*/, std::move(traceId)); + event->Rewrite(TEvTabletPipe::EvSend, pipe); + SendEvent(std::move(event)); +} + +void TViewerPipeClient::SendDelayedRequests() { + while (!DelayedRequests.empty() && Requests < MaxRequestsInFlight) { + auto& request(DelayedRequests.front()); + TActivationContext::Send(request.Event.release()); + ++Requests; + DelayedRequests.pop_front(); + } +} + +TPathId TViewerPipeClient::GetPathId(const TEvTxProxySchemeCache::TEvNavigateKeySetResult& ev) { + if (ev.Request->ResultSet.size() == 1) { + if (ev.Request->ResultSet.begin()->Self) { + const auto& info = ev.Request->ResultSet.begin()->Self->Info; + return TPathId(info.GetSchemeshardId(), info.GetPathId()); + } + if (ev.Request->ResultSet.begin()->TableId) { + return ev.Request->ResultSet.begin()->TableId.PathId; + } + } + return {}; +} + +TString TViewerPipeClient::GetPath(const TEvTxProxySchemeCache::TEvNavigateKeySetResult& ev) { + if (ev.Request->ResultSet.size() == 1) { + return CanonizePath(ev.Request->ResultSet.begin()->Path); + } + return {}; +} + +TPathId TViewerPipeClient::GetPathId(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + return GetPathId(*ev->Get()); +} + +TString TViewerPipeClient::GetPath(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + return GetPath(*ev->Get()); +} + +bool TViewerPipeClient::IsSuccess(const std::unique_ptr& ev) { + return (ev->Request->ResultSet.size() > 0) && (std::find_if(ev->Request->ResultSet.begin(), ev->Request->ResultSet.end(), + [](const auto& entry) { + return entry.Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok; + }) != ev->Request->ResultSet.end()); +} + +TString TViewerPipeClient::GetError(const std::unique_ptr& ev) { + if (ev->Request->ResultSet.size() == 0) { + return "empty response"; + } + for (const auto& entry : ev->Request->ResultSet) { + if (entry.Status != NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { + switch (entry.Status) { + case NSchemeCache::TSchemeCacheNavigate::EStatus::Ok: + return "Ok"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::Unknown: + return "Unknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::RootUnknown: + return "RootUnknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathErrorUnknown: + return "PathErrorUnknown"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotTable: + return "PathNotTable"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotPath: + return "PathNotPath"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::TableCreationNotComplete: + return "TableCreationNotComplete"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::LookupError: + return "LookupError"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::RedirectLookupError: + return "RedirectLookupError"; + case NSchemeCache::TSchemeCacheNavigate::EStatus::AccessDenied: + return "AccessDenied"; + default: + return ::ToString(static_cast(ev->Request->ResultSet.begin()->Status)); + } + } + } + return "no error"; +} + +bool TViewerPipeClient::IsSuccess(const std::unique_ptr& ev) { + return ev->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok; +} + +TString TViewerPipeClient::GetError(const std::unique_ptr& ev) { + switch (ev->Status) { + case TEvStateStorage::TEvBoardInfo::EStatus::Unknown: + return "Unknown"; + case TEvStateStorage::TEvBoardInfo::EStatus::Ok: + return "Ok"; + case TEvStateStorage::TEvBoardInfo::EStatus::NotAvailable: + return "NotAvailable"; + default: + return ::ToString(static_cast(ev->Status)); + } +} + +void TViewerPipeClient::RequestHiveDomainStats(NNodeWhiteboard::TTabletId hiveId) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + THolder request = MakeHolder(); + request->Record.SetReturnFollowers(Followers); + request->Record.SetReturnMetrics(Metrics); + SendRequestToPipe(pipeClient, request.Release(), hiveId); +} + +void TViewerPipeClient::RequestHiveNodeStats(NNodeWhiteboard::TTabletId hiveId, TPathId pathId) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + THolder request = MakeHolder(); + request->Record.SetReturnMetrics(Metrics); + if (pathId != TPathId()) { + request->Record.SetReturnExtendedTabletInfo(true); + request->Record.SetFilterTabletsBySchemeShardId(pathId.OwnerId); + request->Record.SetFilterTabletsByPathId(pathId.LocalPathId); + } + SendRequestToPipe(pipeClient, request.Release(), hiveId); +} + +void TViewerPipeClient::RequestHiveStorageStats(NNodeWhiteboard::TTabletId hiveId) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + THolder request = MakeHolder(); + SendRequestToPipe(pipeClient, request.Release(), hiveId); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeViewerRequest(TNodeId nodeId, TEvViewer::TEvViewerRequest* ev, ui32 flags) { + TActorId viewerServiceId = MakeViewerID(nodeId); + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::THttp::Detailed, TypeName(*ev))); + if (response.Span) { + response.Span.Attribute("target_node_id", nodeId); + TStringBuilder askFor; + askFor << ev->Record.GetLocation().NodeIdSize() << " nodes ("; + for (size_t i = 0; i < std::min(ev->Record.GetLocation().NodeIdSize(), 16); ++i) { + if (i) { + askFor << ", "; + } + askFor << ev->Record.GetLocation().GetNodeId(i); + } + if (ev->Record.GetLocation().NodeIdSize() > 16) { + askFor << ", ..."; + } + askFor << ")"; + response.Span.Attribute("ask_for", askFor); + switch (ev->Record.Request_case()) { + case NKikimrViewer::TEvViewerRequest::kTabletRequest: + response.Span.Attribute("request_type", "TabletRequest"); + break; + case NKikimrViewer::TEvViewerRequest::kSystemRequest: + response.Span.Attribute("request_type", "SystemRequest"); + break; + case NKikimrViewer::TEvViewerRequest::kQueryRequest: + response.Span.Attribute("request_type", "QueryRequest"); + break; + case NKikimrViewer::TEvViewerRequest::kRenderRequest: + response.Span.Attribute("request_type", "RenderRequest"); + break; + case NKikimrViewer::TEvViewerRequest::kAutocompleteRequest: + response.Span.Attribute("request_type", "AutocompleteRequest"); + break; + default: + response.Span.Attribute("request_type", ::ToString(static_cast(ev->Record.Request_case()))); + break; + } + } + SendRequest(viewerServiceId, ev, flags, nodeId, response.Span.GetTraceId()); + return response; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestHiveDomainStats(NNodeWhiteboard::TTabletId hiveId) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + THolder request = MakeHolder(); + request->Record.SetReturnFollowers(Followers); + request->Record.SetReturnMetrics(Metrics); + auto response = MakeRequestToPipe(pipeClient, request.Release(), hiveId); + if (response.Span) { + auto hive_id = "#" + ::ToString(hiveId); + response.Span.Attribute("hive_id", hive_id); + } + return response; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestHiveStorageStats(NNodeWhiteboard::TTabletId hiveId) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + THolder request = MakeHolder(); + auto response = MakeRequestToPipe(pipeClient, request.Release(), hiveId); + if (response.Span) { + auto hive_id = "#" + ::ToString(hiveId); + response.Span.Attribute("hive_id", hive_id); + } + return response; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestHiveNodeStats(TTabletId hiveId, TEvHive::TEvRequestHiveNodeStats* request) { + TActorId pipeClient = ConnectTabletPipe(hiveId); + auto response = MakeRequestToPipe(pipeClient, request, hiveId); + if (response.Span) { + auto hive_id = "#" + ::ToString(hiveId); + response.Span.Attribute("hive_id", hive_id); + } + return response; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestViewer(TNodeId nodeId, TEvViewer::TEvViewerRequest* request, ui32 flags) { + auto requestType = request->Record.GetRequestCase(); + auto response = MakeRequest(MakeViewerID(nodeId), request, flags, nodeId); + if (response.Span) { + TString requestTypeString; + switch (requestType) { + case NKikimrViewer::TEvViewerRequest::kTabletRequest: + requestTypeString = "TabletRequest"; + break; + case NKikimrViewer::TEvViewerRequest::kSystemRequest: + requestTypeString = "SystemRequest"; + break; + case NKikimrViewer::TEvViewerRequest::kQueryRequest: + requestTypeString = "QueryRequest"; + break; + case NKikimrViewer::TEvViewerRequest::kRenderRequest: + requestTypeString = "RenderRequest"; + break; + case NKikimrViewer::TEvViewerRequest::kAutocompleteRequest: + requestTypeString = "AutocompleteRequest"; + break; + default: + requestTypeString = ::ToString(static_cast(requestType)); + break; + } + response.Span.Attribute("request_type", requestTypeString); + } + return response; +} + +void TViewerPipeClient::RequestConsoleListTenants() { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + THolder request = MakeHolder(); + SendRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestConsoleListTenants() { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + THolder request = MakeHolder(); + return MakeRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestConsoleNodeConfigByTenant(TString tenant, ui64 cookie) { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + auto request = MakeHolder(); + request->Record.MutableNode()->SetTenant(tenant); + request->Record.AddItemKinds(static_cast(NKikimrConsole::TConfigItem::FeatureFlagsItem)); + return MakeRequestToPipe(pipeClient, request.Release(), cookie); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestConsoleGetAllConfigs() { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + return MakeRequestToPipe(pipeClient, new NConsole::TEvConsole::TEvGetAllConfigsRequest()); +} + +void TViewerPipeClient::RequestConsoleGetTenantStatus(const TString& path) { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + THolder request = MakeHolder(); + request->Record.MutableRequest()->set_path(path); + SendRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestConsoleGetTenantStatus(const TString& path) { + TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); + THolder request = MakeHolder(); + request->Record.MutableRequest()->set_path(path); + auto response = MakeRequestToPipe(pipeClient, request.Release()); + if (response.Span) { + response.Span.Attribute("path", path); + } + return response; +} + +void TViewerPipeClient::RequestBSControllerConfig() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + request->Record.MutableRequest()->AddCommand()->MutableQueryBaseConfig(); + SendRequestToPipe(pipeClient, request.Release()); +} + +void TViewerPipeClient::RequestBSControllerConfigWithStoragePools() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + request->Record.MutableRequest()->AddCommand()->MutableQueryBaseConfig(); + request->Record.MutableRequest()->AddCommand()->MutableReadStoragePool()->SetBoxId(Max()); + SendRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestBSControllerConfigWithStoragePools() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + request->Record.MutableRequest()->AddCommand()->MutableQueryBaseConfig(); + request->Record.MutableRequest()->AddCommand()->MutableReadStoragePool()->SetBoxId(Max()); + return MakeRequestToPipe(pipeClient, request.Release()); +} + +void TViewerPipeClient::RequestBSControllerInfo() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + SendRequestToPipe(pipeClient, request.Release()); +} + +void TViewerPipeClient::RequestBSControllerSelectGroups(THolder request) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + SendRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestBSControllerSelectGroups(THolder request, ui64 cookie) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + return MakeRequestToPipe(pipeClient, request.Release(), cookie); +} + +void TViewerPipeClient::RequestBSControllerPDiskRestart(ui32 nodeId, ui32 pdiskId, bool force) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + auto* restartPDisk = request->Record.MutableRequest()->AddCommand()->MutableRestartPDisk(); + restartPDisk->MutableTargetPDiskId()->SetNodeId(nodeId); + restartPDisk->MutableTargetPDiskId()->SetPDiskId(pdiskId); + if (force) { + request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); + } + SendRequestToPipe(pipeClient, request.Release()); +} + +void TViewerPipeClient::RequestBSControllerVDiskEvict(ui32 groupId, ui32 groupGeneration, ui32 failRealmIdx, ui32 failDomainIdx, ui32 vdiskIdx, bool force) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + auto* evictVDisk = request->Record.MutableRequest()->AddCommand()->MutableReassignGroupDisk(); + evictVDisk->SetGroupId(groupId); + evictVDisk->SetGroupGeneration(groupGeneration); + evictVDisk->SetFailRealmIdx(failRealmIdx); + evictVDisk->SetFailDomainIdx(failDomainIdx); + evictVDisk->SetVDiskIdx(vdiskIdx); + if (force) { + request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); + } + SendRequestToPipe(pipeClient, request.Release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerPDiskInfo(ui32 nodeId, ui32 pdiskId) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + request->Record.SetInclusiveFrom(true); + request->Record.SetInclusiveTo(true); + request->Record.MutableFrom()->SetNodeId(nodeId); + request->Record.MutableFrom()->SetPDiskId(pdiskId); + request->Record.MutableTo()->SetNodeId(nodeId); + request->Record.MutableTo()->SetPDiskId(pdiskId); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerVDiskInfo(ui32 nodeId, ui32 pdiskId) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + request->Record.SetInclusiveFrom(true); + request->Record.SetInclusiveTo(true); + request->Record.MutableFrom()->SetNodeId(nodeId); + request->Record.MutableFrom()->SetPDiskId(pdiskId); + request->Record.MutableFrom()->SetVSlotId(0); + request->Record.MutableTo()->SetNodeId(nodeId); + request->Record.MutableTo()->SetPDiskId(pdiskId); + request->Record.MutableTo()->SetVSlotId(std::numeric_limits::max()); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerGroups() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerPools() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerVSlots() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerPDisks() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + auto request = std::make_unique(); + return MakeRequestToPipe(pipeClient, request.release()); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::RequestBSControllerStorageStats() { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + return MakeRequestToPipe(pipeClient, new NSysView::TEvSysView::TEvGetStorageStatsRequest()); +} + +void TViewerPipeClient::RequestBSControllerPDiskUpdateStatus(const NKikimrBlobStorage::TUpdateDriveStatus& driveStatus, bool force) { + TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); + THolder request = MakeHolder(); + auto* updateDriveStatus = request->Record.MutableRequest()->AddCommand()->MutableUpdateDriveStatus(); + updateDriveStatus->CopyFrom(driveStatus); + if (force) { + request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); + } + SendRequestToPipe(pipeClient, request.Release()); +} + +void TViewerPipeClient::RequestSchemeCacheNavigate(const TString& path) { + THolder request = MakeHolder(); + NSchemeCache::TSchemeCacheNavigate::TEntry entry; + entry.Path = SplitPath(path); + entry.RedirectRequired = false; + entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; + request->ResultSet.emplace_back(entry); + SendRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); +} + +void TViewerPipeClient::RequestSchemeCacheNavigate(const TPathId& pathId) { + THolder request = MakeHolder(); + NSchemeCache::TSchemeCacheNavigate::TEntry entry; + entry.TableId.PathId = pathId; + entry.RequestType = NSchemeCache::TSchemeCacheNavigate::TEntry::ERequestType::ByTableId; + entry.RedirectRequired = false; + entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; + request->ResultSet.emplace_back(entry); + SendRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestSchemeCacheNavigate(const TString& path, ui64 cookie) { + THolder request = MakeHolder(); + NSchemeCache::TSchemeCacheNavigate::TEntry entry; + entry.Path = SplitPath(path); + entry.RedirectRequired = false; + entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; + request->ResultSet.emplace_back(entry); + auto response = MakeRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release()), 0 /*flags*/, cookie); + if (response.Span) { + response.Span.Attribute("path", path); + } + return response; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestSchemeCacheNavigate(TPathId pathId, ui64 cookie) { + THolder request = MakeHolder(); + NSchemeCache::TSchemeCacheNavigate::TEntry entry; + entry.TableId.PathId = pathId; + entry.RequestType = NSchemeCache::TSchemeCacheNavigate::TEntry::ERequestType::ByTableId; + entry.RedirectRequired = false; + entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; + request->ResultSet.emplace_back(entry); + auto response = MakeRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release()), 0 /*flags*/, cookie); + if (response.Span) { + response.Span.Attribute("path_id", pathId.ToString()); + } + return response; +} + +void TViewerPipeClient::RequestTxProxyDescribe(const TString& path) { + THolder request(new TEvTxUserProxy::TEvNavigate()); + request->Record.MutableDescribePath()->SetPath(path); + SendRequest(MakeTxProxyID(), request.Release()); +} + +void TViewerPipeClient::RequestStateStorageEndpointsLookup(const TString& path) { + RegisterWithSameMailbox(CreateBoardLookupActor(MakeEndpointsBoardPath(path), + SelfId(), + EBoardLookupMode::Second)); + ++Requests; +} + +TViewerPipeClient::TRequestResponse TViewerPipeClient::MakeRequestStateStorageEndpointsLookup(const TString& path, ui64 cookie) { + TRequestResponse response(Span.CreateChild(TComponentTracingLevels::THttp::Detailed, "BoardLookupActor")); + RegisterWithSameMailbox(CreateBoardLookupActor(MakeEndpointsBoardPath(path), + SelfId(), + EBoardLookupMode::Second, {}, cookie)); + if (response.Span) { + response.Span.Attribute("path", path); + } + ++Requests; + return response; +} + +void TViewerPipeClient::RequestStateStorageMetadataCacheEndpointsLookup(const TString& path) { + if (!AppData()->DomainsInfo->Domain) { + return; + } + RegisterWithSameMailbox(CreateBoardLookupActor(MakeDatabaseMetadataCacheBoardPath(path), + SelfId(), + EBoardLookupMode::Second)); + ++Requests; +} + +std::vector TViewerPipeClient::GetNodesFromBoardReply(const TEvStateStorage::TEvBoardInfo& ev) { + std::vector databaseNodes; + if (ev.Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { + for (const auto& [actorId, infoEntry] : ev.InfoEntries) { + databaseNodes.emplace_back(actorId.NodeId()); + } + } + std::sort(databaseNodes.begin(), databaseNodes.end()); + databaseNodes.erase(std::unique(databaseNodes.begin(), databaseNodes.end()), databaseNodes.end()); + return databaseNodes; +} + +std::vector TViewerPipeClient::GetNodesFromBoardReply(TEvStateStorage::TEvBoardInfo::TPtr& ev) { + return GetNodesFromBoardReply(*ev->Get()); +} + +void TViewerPipeClient::InitConfig(const TCgiParameters& params) { + Followers = FromStringWithDefault(params.Get("followers"), Followers); + Metrics = FromStringWithDefault(params.Get("metrics"), Metrics); + WithRetry = FromStringWithDefault(params.Get("with_retry"), WithRetry); + MaxRequestsInFlight = FromStringWithDefault(params.Get("max_requests_in_flight"), MaxRequestsInFlight); + Database = params.Get("database"); + if (!Database) { + Database = params.Get("tenant"); + } + Direct = FromStringWithDefault(params.Get("direct"), Direct); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), true); + Timeout = TDuration::MilliSeconds(FromStringWithDefault(params.Get("timeout"), Timeout.MilliSeconds())); +} + +void TViewerPipeClient::InitConfig(const TRequestSettings& settings) { + Followers = settings.Followers; + Metrics = settings.Metrics; + WithRetry = settings.WithRetry; +} + +void TViewerPipeClient::ClosePipes() { + for (const auto& [tabletId, pipeInfo] : PipeInfo) { + if (pipeInfo.PipeClient) { + NTabletPipe::CloseClient(SelfId(), pipeInfo.PipeClient); + } + } + PipeInfo.clear(); +} + +ui32 TViewerPipeClient::FailPipeConnect(NNodeWhiteboard::TTabletId tabletId) { + auto itPipeInfo = PipeInfo.find(tabletId); + if (itPipeInfo != PipeInfo.end()) { + ui32 requests = itPipeInfo->second.Requests; + NTabletPipe::CloseClient(SelfId(), itPipeInfo->second.PipeClient); + PipeInfo.erase(itPipeInfo); + return requests; + } + return 0; +} + +TRequestState TViewerPipeClient::GetRequest() const { + return {Event->Get(), Span.GetTraceId()}; +} + +void TViewerPipeClient::ReplyAndPassAway(TString data, const TString& error) { + Send(Event->Sender, new NMon::TEvHttpInfoRes(data, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + if (Span) { + if (error) { + Span.EndError(error); + } else { + Span.EndOk(); + } + } + PassAway(); +} + +TString TViewerPipeClient::GetHTTPOK(TString contentType, TString response, TInstant lastModified) { + return Viewer->GetHTTPOK(GetRequest(), std::move(contentType), std::move(response), lastModified); +} + +TString TViewerPipeClient::GetHTTPOKJSON(TString response, TInstant lastModified) { + return Viewer->GetHTTPOKJSON(GetRequest(), std::move(response), lastModified); +} + +TString TViewerPipeClient::GetHTTPOKJSON(const NJson::TJsonValue& response, TInstant lastModified) { + return GetHTTPOKJSON(NJson::WriteJson(response, false), lastModified); +} + +TString TViewerPipeClient::GetHTTPOKJSON(const google::protobuf::Message& response, TInstant lastModified) { + TStringStream json; + TProtoToJson::ProtoToJson(json, response, JsonSettings); + return GetHTTPOKJSON(json.Str(), lastModified); +} + +TString TViewerPipeClient::GetHTTPGATEWAYTIMEOUT(TString contentType, TString response) { + return Viewer->GetHTTPGATEWAYTIMEOUT(GetRequest(), std::move(contentType), std::move(response)); +} + +TString TViewerPipeClient::GetHTTPBADREQUEST(TString contentType, TString response) { + return Viewer->GetHTTPBADREQUEST(GetRequest(), std::move(contentType), std::move(response)); +} + +TString TViewerPipeClient::GetHTTPINTERNALERROR(TString contentType, TString response) { + return Viewer->GetHTTPINTERNALERROR(GetRequest(), std::move(contentType), std::move(response)); +} + +TString TViewerPipeClient::GetHTTPFORBIDDEN(TString contentType, TString response) { + return Viewer->GetHTTPFORBIDDEN(GetRequest(), std::move(contentType), std::move(response)); +} + +TString TViewerPipeClient::MakeForward(const std::vector& nodes) { + return Viewer->MakeForward(GetRequest(), nodes); +} + +void TViewerPipeClient::RequestDone(ui32 requests) { + if (requests == 0) { + return; + } + Requests -= requests; + if (!DelayedRequests.empty()) { + SendDelayedRequests(); + } + if (Requests == 0) { + ReplyAndPassAway(); + } +} + +void TViewerPipeClient::Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { + if (ev->Get()->Status != NKikimrProto::OK) { + ui32 requests = FailPipeConnect(ev->Get()->TabletId); + RequestDone(requests); + } +} + +void TViewerPipeClient::HandleResolveResource(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + if (ResourceNavigateResponse) { + ResourceNavigateResponse->Set(std::move(ev)); + if (ResourceNavigateResponse->IsOk()) { + TSchemeCacheNavigate::TEntry& entry(ResourceNavigateResponse->Get()->Request->ResultSet.front()); + SharedDatabase = CanonizePath(entry.Path); + if (SharedDatabase == AppData()->TenantName) { + Direct = true; + return Bootstrap(); // retry bootstrap without redirect this time + } + DatabaseBoardInfoResponse = MakeRequestStateStorageEndpointsLookup(SharedDatabase); + } else { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "Failed to resolve database - shared database not found")); + } + } +} + +void TViewerPipeClient::HandleResolveDatabase(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + if (DatabaseNavigateResponse) { + DatabaseNavigateResponse->Set(std::move(ev)); + if (DatabaseNavigateResponse->IsOk()) { + TSchemeCacheNavigate::TEntry& entry(DatabaseNavigateResponse->Get()->Request->ResultSet.front()); + if (entry.DomainInfo && entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { + ResourceNavigateResponse = MakeRequestSchemeCacheNavigate(TPathId(entry.DomainInfo->ResourcesDomainKey)); + Become(&TViewerPipeClient::StateResolveResource); + return; + } + DatabaseBoardInfoResponse = MakeRequestStateStorageEndpointsLookup(CanonizePath(entry.Path)); + } else { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "Failed to resolve database - not found")); + } + } +} + +void TViewerPipeClient::HandleResolve(TEvStateStorage::TEvBoardInfo::TPtr& ev) { + if (DatabaseBoardInfoResponse) { + DatabaseBoardInfoResponse->Set(std::move(ev)); + if (DatabaseBoardInfoResponse->IsOk()) { + ReplyAndPassAway(MakeForward(GetNodesFromBoardReply(DatabaseBoardInfoResponse->GetRef()))); + } else { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "Failed to resolve database - no nodes found")); + } + } +} + +void TViewerPipeClient::HandleTimeout() { + ReplyAndPassAway(GetHTTPGATEWAYTIMEOUT()); +} + +STATEFN(TViewerPipeClient::StateResolveDatabase) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvStateStorage::TEvBoardInfo, HandleResolve); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, HandleResolveDatabase); + cFunc(TEvents::TEvWakeup::EventType, HandleTimeout); + } +} + +STATEFN(TViewerPipeClient::StateResolveResource) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvStateStorage::TEvBoardInfo, HandleResolve); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, HandleResolveResource); + cFunc(TEvents::TEvWakeup::EventType, HandleTimeout); + } +} + +void TViewerPipeClient::RedirectToDatabase(const TString& database) { + DatabaseNavigateResponse = MakeRequestSchemeCacheNavigate(database); + Become(&TViewerPipeClient::StateResolveDatabase); +} + +bool TViewerPipeClient::NeedToRedirect() { + if (Event) { + Direct |= !Event->Get()->Request.GetHeader("X-Forwarded-From-Node").empty(); // we're already forwarding + Direct |= (Database == AppData()->TenantName) || Database.empty(); // we're already on the right node or don't use database filter + if (Database && !Direct) { + RedirectToDatabase(Database); // to find some dynamic node and redirect query there + return true; + } + } + return false; +} + +void TViewerPipeClient::PassAway() { + std::sort(SubscriptionNodeIds.begin(), SubscriptionNodeIds.end()); + SubscriptionNodeIds.erase(std::unique(SubscriptionNodeIds.begin(), SubscriptionNodeIds.end()), SubscriptionNodeIds.end()); + for (TNodeId nodeId : SubscriptionNodeIds) { + Send(TActivationContext::InterconnectProxy(nodeId), new TEvents::TEvUnsubscribe()); + } + ClosePipes(); + TBase::PassAway(); +} + +void TViewerPipeClient::AddEvent(const TString& name) { + if (Span) { + Span.Event(name); + } +} + +} diff --git a/ydb/core/viewer/json_pipe_req.h b/ydb/core/viewer/json_pipe_req.h index b37026c1826d..e5f7665f74f2 100644 --- a/ydb/core/viewer/json_pipe_req.h +++ b/ydb/core/viewer/json_pipe_req.h @@ -1,54 +1,66 @@ #pragma once - -#include -#include -#include -#include -#include -#include +#include "viewer.h" #include #include +#include #include +#include #include #include +#include #include #include #include -#include -#include "viewer.h" +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NKikimr; using namespace NSchemeCache; using NNodeWhiteboard::TNodeId; +using NNodeWhiteboard::TTabletId; + +class TViewerPipeClient : public TActorBootstrapped { + using TBase = TActorBootstrapped; + +public: + static constexpr NKikimrServices::TActivity::EType ActorActivityType() { + return NKikimrServices::TActivity::VIEWER_HANDLER; + } + + virtual void Bootstrap() = 0; + virtual void ReplyAndPassAway() = 0; -template -class TViewerPipeClient : public TActorBootstrapped { protected: - using TBase = TActorBootstrapped; bool Followers = true; bool Metrics = true; bool WithRetry = true; + TString Database; + TString SharedDatabase; + bool Direct = false; ui32 Requests = 0; - static constexpr ui32 MaxRequestsInFlight = 50; + ui32 MaxRequestsInFlight = 200; NWilson::TSpan Span; IViewer* Viewer = nullptr; NMon::TEvHttpInfo::TPtr Event; + TJsonSettings JsonSettings; + TDuration Timeout = TDuration::Seconds(10); struct TPipeInfo { TActorId PipeClient; ui32 Requests = 0; }; - std::unordered_map PipeInfo; + std::unordered_map PipeInfo; struct TDelayedRequest { std::unique_ptr Event; }; std::deque DelayedRequests; + std::vector SubscriptionNodeIds; template struct TRequestResponse { @@ -66,23 +78,30 @@ class TViewerPipeClient : public TActorBootstrapped { TRequestResponse& operator =(TRequestResponse&&) = default; void Set(std::unique_ptr&& response) { + constexpr bool hasErrorCheck = requires(const std::unique_ptr& r) {TViewerPipeClient::IsSuccess(r);}; + if constexpr (hasErrorCheck) { + if (!TViewerPipeClient::IsSuccess(response)) { + Error(TViewerPipeClient::GetError(response)); + return; + } + } if (!IsDone()) { Span.EndOk(); + Response = std::move(response); } - Response = std::move(response); } void Set(TAutoPtr>&& response) { Set(std::unique_ptr(response->Release().Release())); } - void Error(const TString& error) { + bool Error(const TString& error) { if (!IsDone()) { Span.EndError(error); - } - if (!IsOk()) { Response = error; + return true; } + return false; } bool IsOk() const { @@ -105,386 +124,192 @@ class TViewerPipeClient : public TActorBootstrapped { return std::get>(Response).get(); } - T* operator ->() { + const T* Get() const { return std::get>(Response).get(); } - TString GetError() const { - return std::get(Response); + T& GetRef() { + return *Get(); } - }; - NTabletPipe::TClientConfig GetPipeClientConfig() { - NTabletPipe::TClientConfig clientConfig; - if (WithRetry) { - clientConfig.RetryPolicy = {.RetryLimitCount = 3}; + const T& GetRef() const { + return *Get(); } - return clientConfig; - } - TViewerPipeClient() = default; - - TViewerPipeClient(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Event(ev) - { - InitConfig(Event->Get()->Request.GetParams()); - NWilson::TTraceId traceId; - TStringBuf traceparent = Event->Get()->Request.GetHeader("traceparent"); - if (traceparent) { - traceId = NWilson::TTraceId::FromTraceparentHeader(traceparent, TComponentTracingLevels::ProductionVerbose); + T* operator ->() { + return Get(); } - TStringBuf wantTrace = Event->Get()->Request.GetHeader("X-Want-Trace"); - if (!traceId && FromStringWithDefault(wantTrace)) { - traceId = NWilson::TTraceId::NewTraceId(TComponentTracingLevels::ProductionVerbose, Max()); + + const T* operator ->() const { + return Get(); } - if (traceId) { - Span = {TComponentTracingLevels::THttp::TopLevel, std::move(traceId), "http", NWilson::EFlags::AUTO_END}; - Span.Attribute("request_type", TString(Event->Get()->Request.GetUri().Before('?'))); + + T& operator *() { + return GetRef(); } - } - TActorId ConnectTabletPipe(NNodeWhiteboard::TTabletId tabletId) { - TPipeInfo& pipeInfo = PipeInfo[tabletId]; - if (!pipeInfo.PipeClient) { - auto pipe = NTabletPipe::CreateClient(TBase::SelfId(), tabletId, GetPipeClientConfig()); - pipeInfo.PipeClient = TBase::RegisterWithSameMailbox(pipe); + const T& operator *() const { + return GetRef(); } - pipeInfo.Requests++; - return pipeInfo.PipeClient; - } - void SendEvent(std::unique_ptr event) { - if (DelayedRequests.empty() && Requests < MaxRequestsInFlight) { - TActivationContext::Send(event.release()); - ++Requests; - } else { - DelayedRequests.push_back({ - .Event = std::move(event), - }); + TString GetError() const { + return std::get(Response); } - } - void SendRequest(const TActorId& recipient, IEventBase* ev, ui32 flags = 0, ui64 cookie = 0, NWilson::TTraceId traceId = {}) { - SendEvent(std::make_unique(recipient, TBase::SelfId(), ev, flags, cookie, nullptr/*forwardOnNondelivery*/, std::move(traceId))); - } + void Event(const TString& name) { + if (Span) { + Span.Event(name); + } + } + }; - void SendRequestToPipe(const TActorId& pipe, IEventBase* ev, ui64 cookie = 0, NWilson::TTraceId traceId = {}) { - std::unique_ptr event = std::make_unique(pipe, TBase::SelfId(), ev, 0/*flags*/, cookie, nullptr/*forwardOnNondelivery*/, std::move(traceId)); - event->Rewrite(TEvTabletPipe::EvSend, pipe); - SendEvent(std::move(event)); - } + std::optional> DatabaseNavigateResponse; + std::optional> ResourceNavigateResponse; + std::optional> DatabaseBoardInfoResponse; + + NTabletPipe::TClientConfig GetPipeClientConfig(); + + ~TViewerPipeClient(); + TViewerPipeClient(); + TViewerPipeClient(NWilson::TTraceId traceId); + TViewerPipeClient(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev, const TString& handlerName = {}); + TActorId ConnectTabletPipe(TTabletId tabletId); + void SendEvent(std::unique_ptr event); + void SendRequest(TActorId recipient, IEventBase* ev, ui32 flags = 0, ui64 cookie = 0, NWilson::TTraceId traceId = {}); + void SendRequestToPipe(TActorId pipe, IEventBase* ev, ui64 cookie = 0, NWilson::TTraceId traceId = {}); template - TRequestResponse MakeRequest(const TActorId& recipient, IEventBase* ev, ui32 flags = 0, ui64 cookie = 0) { + TRequestResponse MakeRequest(TActorId recipient, IEventBase* ev, ui32 flags = 0, ui64 cookie = 0) { TRequestResponse response(Span.CreateChild(TComponentTracingLevels::THttp::Detailed, TypeName(*ev))); SendRequest(recipient, ev, flags, cookie, response.Span.GetTraceId()); + if (flags & IEventHandle::FlagSubscribeOnSession) { + SubscriptionNodeIds.push_back(recipient.NodeId()); + } return response; } template - TRequestResponse MakeRequestToPipe(const TActorId& pipe, IEventBase* ev, ui64 cookie = 0) { + TRequestResponse MakeRequestToPipe(TActorId pipe, IEventBase* ev, ui64 cookie = 0) { TRequestResponse response(Span.CreateChild(TComponentTracingLevels::THttp::Detailed, TypeName(*ev))); SendRequestToPipe(pipe, ev, cookie, response.Span.GetTraceId()); return response; } - void SendDelayedRequests() { - while (!DelayedRequests.empty() && Requests < MaxRequestsInFlight) { - auto& request(DelayedRequests.front()); - TActivationContext::Send(request.Event.release()); - ++Requests; - DelayedRequests.pop_front(); - } - } - - void RequestHiveDomainStats(NNodeWhiteboard::TTabletId hiveId) { - TActorId pipeClient = ConnectTabletPipe(hiveId); - THolder request = MakeHolder(); - request->Record.SetReturnFollowers(Followers); - request->Record.SetReturnMetrics(Metrics); - SendRequestToPipe(pipeClient, request.Release(), hiveId); - } - - void RequestHiveNodeStats(NNodeWhiteboard::TTabletId hiveId, TPathId pathId) { - TActorId pipeClient = ConnectTabletPipe(hiveId); - THolder request = MakeHolder(); - request->Record.SetReturnMetrics(Metrics); - if (pathId != TPathId()) { - request->Record.SetReturnExtendedTabletInfo(true); - request->Record.SetFilterTabletsBySchemeShardId(pathId.OwnerId); - request->Record.SetFilterTabletsByPathId(pathId.LocalPathId); + template + TRequestResponse::Type> MakeWhiteboardRequest(TNodeId nodeId, TRequest* ev, ui32 flags = IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession) { + TActorId whiteboardServiceId = NNodeWhiteboard::MakeNodeWhiteboardServiceId(nodeId); + TRequestResponse::Type> response(Span.CreateChild(TComponentTracingLevels::THttp::Detailed, TypeName(*ev))); + if (response.Span) { + response.Span.Attribute("target_node_id", nodeId); } - SendRequestToPipe(pipeClient, request.Release(), hiveId); + SendRequest(whiteboardServiceId, ev, flags, nodeId, response.Span.GetTraceId()); + return response; } - void RequestHiveStorageStats(NNodeWhiteboard::TTabletId hiveId) { - TActorId pipeClient = ConnectTabletPipe(hiveId); - THolder request = MakeHolder(); - SendRequestToPipe(pipeClient, request.Release(), hiveId); - } + TRequestResponse MakeViewerRequest(TNodeId nodeId, TEvViewer::TEvViewerRequest* ev, ui32 flags = IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession); + void SendDelayedRequests(); + void RequestHiveDomainStats(TTabletId hiveId); + void RequestHiveNodeStats(TTabletId hiveId, TPathId pathId); + void RequestHiveStorageStats(TTabletId hiveId); - NNodeWhiteboard::TTabletId GetConsoleId() { + TTabletId GetConsoleId() { return MakeConsoleID(); } - void RequestConsoleListTenants() { - TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); - THolder request = MakeHolder(); - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestConsoleGetTenantStatus(const TString& path) { - TActorId pipeClient = ConnectTabletPipe(GetConsoleId()); - THolder request = MakeHolder(); - request->Record.MutableRequest()->set_path(path); - SendRequestToPipe(pipeClient, request.Release()); - } - - NNodeWhiteboard::TTabletId GetBSControllerId() { + TTabletId GetBSControllerId() { return MakeBSControllerID(); } - void RequestBSControllerConfig() { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - request->Record.MutableRequest()->AddCommand()->MutableQueryBaseConfig(); - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestBSControllerConfigWithStoragePools() { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - request->Record.MutableRequest()->AddCommand()->MutableQueryBaseConfig(); - request->Record.MutableRequest()->AddCommand()->MutableReadStoragePool()->SetBoxId(Max()); - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestBSControllerInfo() { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestBSControllerSelectGroups(THolder request) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestBSControllerPDiskRestart(ui32 nodeId, ui32 pdiskId, bool force = false) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - auto* restartPDisk = request->Record.MutableRequest()->AddCommand()->MutableRestartPDisk(); - restartPDisk->MutableTargetPDiskId()->SetNodeId(nodeId); - restartPDisk->MutableTargetPDiskId()->SetPDiskId(pdiskId); - if (force) { - request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); - } - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestBSControllerVDiskEvict(ui32 groupId, ui32 groupGeneration, ui32 failRealmIdx, ui32 failDomainIdx, ui32 vdiskIdx, bool force = false) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - auto* evictVDisk = request->Record.MutableRequest()->AddCommand()->MutableReassignGroupDisk(); - evictVDisk->SetGroupId(groupId); - evictVDisk->SetGroupGeneration(groupGeneration); - evictVDisk->SetFailRealmIdx(failRealmIdx); - evictVDisk->SetFailDomainIdx(failDomainIdx); - evictVDisk->SetVDiskIdx(vdiskIdx); - if (force) { - request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); - } - SendRequestToPipe(pipeClient, request.Release()); - } - - TRequestResponse RequestBSControllerPDiskInfo(ui32 nodeId, ui32 pdiskId) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - auto request = std::make_unique(); - request->Record.SetInclusiveFrom(true); - request->Record.SetInclusiveTo(true); - request->Record.MutableFrom()->SetNodeId(nodeId); - request->Record.MutableFrom()->SetPDiskId(pdiskId); - request->Record.MutableTo()->SetNodeId(nodeId); - request->Record.MutableTo()->SetPDiskId(pdiskId); - return MakeRequestToPipe(pipeClient, request.release(), 0/*cookie*/); - } - - TRequestResponse RequestBSControllerVDiskInfo(ui32 nodeId, ui32 pdiskId) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - auto request = std::make_unique(); - request->Record.SetInclusiveFrom(true); - request->Record.SetInclusiveTo(true); - request->Record.MutableFrom()->SetNodeId(nodeId); - request->Record.MutableFrom()->SetPDiskId(pdiskId); - request->Record.MutableFrom()->SetVSlotId(0); - request->Record.MutableTo()->SetNodeId(nodeId); - request->Record.MutableTo()->SetPDiskId(pdiskId); - request->Record.MutableTo()->SetVSlotId(std::numeric_limits::max()); - return MakeRequestToPipe(pipeClient, request.release(), 0/*cookie*/); - } - - void RequestBSControllerPDiskUpdateStatus(const NKikimrBlobStorage::TUpdateDriveStatus& driveStatus, bool force = false) { - TActorId pipeClient = ConnectTabletPipe(GetBSControllerId()); - THolder request = MakeHolder(); - auto* updateDriveStatus = request->Record.MutableRequest()->AddCommand()->MutableUpdateDriveStatus(); - updateDriveStatus->CopyFrom(driveStatus); - if (force) { - request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true); - } - SendRequestToPipe(pipeClient, request.Release()); - } - - void RequestSchemeCacheNavigate(const TString& path) { - THolder request = MakeHolder(); - NSchemeCache::TSchemeCacheNavigate::TEntry entry; - entry.Path = SplitPath(path); - entry.RedirectRequired = false; - entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; - request->ResultSet.emplace_back(entry); - SendRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); - } - - void RequestSchemeCacheNavigate(const TPathId& pathId) { - THolder request = MakeHolder(); - NSchemeCache::TSchemeCacheNavigate::TEntry entry; - entry.TableId.PathId = pathId; - entry.RequestType = NSchemeCache::TSchemeCacheNavigate::TEntry::ERequestType::ByTableId; - entry.RedirectRequired = false; - entry.Operation = NSchemeCache::TSchemeCacheNavigate::EOp::OpPath; - request->ResultSet.emplace_back(entry); - SendRequest(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); - } - - void RequestTxProxyDescribe(const TString& path) { - THolder request(new TEvTxUserProxy::TEvNavigate()); - request->Record.MutableDescribePath()->SetPath(path); - SendRequest(MakeTxProxyID(), request.Release()); - } - - void RequestStateStorageEndpointsLookup(const TString& path) { - TBase::RegisterWithSameMailbox(CreateBoardLookupActor(MakeEndpointsBoardPath(path), - TBase::SelfId(), - EBoardLookupMode::Second)); - ++Requests; - } - - void RequestStateStorageMetadataCacheEndpointsLookup(const TString& path) { - if (!AppData()->DomainsInfo->Domain) { - return; - } - TBase::RegisterWithSameMailbox(CreateBoardLookupActor(MakeDatabaseMetadataCacheBoardPath(path), - TBase::SelfId(), - EBoardLookupMode::Second)); - ++Requests; - } - - std::vector GetNodesFromBoardReply(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - std::vector databaseNodes; - if (ev->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { - for (const auto& [actorId, infoEntry] : ev->Get()->InfoEntries) { - databaseNodes.emplace_back(actorId.NodeId()); - } - } - std::sort(databaseNodes.begin(), databaseNodes.end()); - databaseNodes.erase(std::unique(databaseNodes.begin(), databaseNodes.end()), databaseNodes.end()); - return databaseNodes; - } - - void InitConfig(const TCgiParameters& params) { - Followers = FromStringWithDefault(params.Get("followers"), Followers); - Metrics = FromStringWithDefault(params.Get("metrics"), Metrics); - WithRetry = FromStringWithDefault(params.Get("with_retry"), WithRetry); - } - - void InitConfig(const TRequestSettings& settings) { - Followers = settings.Followers; - Metrics = settings.Metrics; - WithRetry = settings.WithRetry; - } - - void ClosePipes() { - for (const auto& [tabletId, pipeInfo] : PipeInfo) { - if (pipeInfo.PipeClient) { - NTabletPipe::CloseClient(TBase::SelfId(), pipeInfo.PipeClient); - } - } - PipeInfo.clear(); - } - - ui32 FailPipeConnect(NNodeWhiteboard::TTabletId tabletId) { - auto itPipeInfo = PipeInfo.find(tabletId); - if (itPipeInfo != PipeInfo.end()) { - ui32 requests = itPipeInfo->second.Requests; - NTabletPipe::CloseClient(TBase::SelfId(), itPipeInfo->second.PipeClient); - PipeInfo.erase(itPipeInfo); - return requests; - } - return 0; - } - - void RequestDone(ui32 requests = 1) { - Requests -= requests; - if (!DelayedRequests.empty()) { - SendDelayedRequests(); - } - if (Requests == 0) { - static_cast(this)->ReplyAndPassAway(); - } - } - - void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { - if (ev->Get()->Status != NKikimrProto::OK) { - ui32 requests = FailPipeConnect(ev->Get()->TabletId); - RequestDone(requests); - } - } - - void PassAway() override { - ClosePipes(); - TBase::PassAway(); - } - - TRequestState GetRequest() const { - return {Event->Get(), Span.GetTraceId()}; - } - - void ReplyAndPassAway(TString data, const TString& error = {}) { - TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(data, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - if (Span) { - if (error) { - Span.EndError(error); - } else { - Span.EndOk(); - } - } - PassAway(); - } - - TString GetHTTPOK(TString contentType = {}, TString response = {}, TInstant lastModified = {}) { - return Viewer->GetHTTPOK(GetRequest(), contentType, response, lastModified); - } - - TString GetHTTPOKJSON(TString response = {}, TInstant lastModified = {}) { - return Viewer->GetHTTPOKJSON(GetRequest(), response, lastModified); - } - - TString GetHTTPGATEWAYTIMEOUT(TString contentType = {}, TString response = {}) { - return Viewer->GetHTTPGATEWAYTIMEOUT(GetRequest(), contentType, response); - } - - TString GetHTTPBADREQUEST(TString contentType = {}, TString response = {}) { - return Viewer->GetHTTPBADREQUEST(GetRequest(), contentType, response); - } - - TString GetHTTPINTERNALERROR(TString contentType = {}, TString response = {}) { - return Viewer->GetHTTPINTERNALERROR(GetRequest(), contentType, response); - } - - TString MakeForward(const std::vector& nodes) { - return Viewer->MakeForward(GetRequest(), nodes); - } + static TPathId GetPathId(const TEvTxProxySchemeCache::TEvNavigateKeySetResult& ev); + static TString GetPath(const TEvTxProxySchemeCache::TEvNavigateKeySetResult& ev); + + static TPathId GetPathId(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev); + static TString GetPath(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev); + + static bool IsSuccess(const std::unique_ptr& ev); + static TString GetError(const std::unique_ptr& ev); + + static bool IsSuccess(const std::unique_ptr& ev); + static TString GetError(const std::unique_ptr& ev); + + TRequestResponse MakeRequestHiveDomainStats(TTabletId hiveId); + TRequestResponse MakeRequestHiveStorageStats(TTabletId hiveId); + TRequestResponse MakeRequestHiveNodeStats(TTabletId hiveId, TEvHive::TEvRequestHiveNodeStats* request); + void RequestConsoleListTenants(); + TRequestResponse MakeRequestConsoleListTenants(); + TRequestResponse MakeRequestConsoleNodeConfigByTenant(TString tenant, ui64 cookie = 0); + TRequestResponse MakeRequestConsoleGetAllConfigs(); + void RequestConsoleGetTenantStatus(const TString& path); + TRequestResponse MakeRequestConsoleGetTenantStatus(const TString& path); + void RequestBSControllerConfig(); + void RequestBSControllerConfigWithStoragePools(); + TRequestResponse MakeRequestBSControllerConfigWithStoragePools(); + void RequestBSControllerInfo(); + void RequestBSControllerSelectGroups(THolder request); + TRequestResponse MakeRequestBSControllerSelectGroups(THolder request, ui64 cookie = 0); + void RequestBSControllerPDiskRestart(ui32 nodeId, ui32 pdiskId, bool force = false); + void RequestBSControllerVDiskEvict(ui32 groupId, ui32 groupGeneration, ui32 failRealmIdx, ui32 failDomainIdx, ui32 vdiskIdx, bool force = false); + TRequestResponse RequestBSControllerPDiskInfo(ui32 nodeId, ui32 pdiskId); + TRequestResponse RequestBSControllerVDiskInfo(ui32 nodeId, ui32 pdiskId); + TRequestResponse RequestBSControllerGroups(); + TRequestResponse RequestBSControllerPools(); + TRequestResponse RequestBSControllerVSlots(); + TRequestResponse RequestBSControllerPDisks(); + TRequestResponse RequestBSControllerStorageStats(); + void RequestBSControllerPDiskUpdateStatus(const NKikimrBlobStorage::TUpdateDriveStatus& driveStatus, bool force = false); + void RequestSchemeCacheNavigate(const TString& path); + void RequestSchemeCacheNavigate(const TPathId& pathId); + TRequestResponse MakeRequestSchemeCacheNavigate(const TString& path, ui64 cookie = 0); + TRequestResponse MakeRequestSchemeCacheNavigate(TPathId pathId, ui64 cookie = 0); + TRequestResponse MakeRequestViewer(TNodeId nodeId, TEvViewer::TEvViewerRequest* request, ui32 flags = 0); + void RequestTxProxyDescribe(const TString& path); + void RequestStateStorageEndpointsLookup(const TString& path); + void RequestStateStorageMetadataCacheEndpointsLookup(const TString& path); + TRequestResponse MakeRequestStateStorageEndpointsLookup(const TString& path, ui64 cookie = 0); + std::vector GetNodesFromBoardReply(TEvStateStorage::TEvBoardInfo::TPtr& ev); + std::vector GetNodesFromBoardReply(const TEvStateStorage::TEvBoardInfo& ev); + void InitConfig(const TCgiParameters& params); + void InitConfig(const TRequestSettings& settings); + void ClosePipes(); + ui32 FailPipeConnect(TTabletId tabletId); + + bool IsLastRequest() const { + return Requests == 1; + } + + bool WaitingForResponse() const { + return Requests != 0; + } + + bool NoMoreRequests(ui32 requestsDone = 0) const { + return Requests == requestsDone; + } + + TRequestState GetRequest() const; + void ReplyAndPassAway(TString data, const TString& error = {}); + + TString GetHTTPOK(TString contentType = {}, TString response = {}, TInstant lastModified = {}); + TString GetHTTPOKJSON(TString response = {}, TInstant lastModified = {}); + TString GetHTTPOKJSON(const NJson::TJsonValue& response, TInstant lastModified = {}); + TString GetHTTPOKJSON(const google::protobuf::Message& response, TInstant lastModified = {}); + TString GetHTTPGATEWAYTIMEOUT(TString contentType = {}, TString response = {}); + TString GetHTTPBADREQUEST(TString contentType = {}, TString response = {}); + TString GetHTTPINTERNALERROR(TString contentType = {}, TString response = {}); + TString GetHTTPFORBIDDEN(TString contentType = {}, TString response = {}); + TString MakeForward(const std::vector& nodes); + + void RequestDone(ui32 requests = 1); + void AddEvent(const TString& name); + void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev); + void HandleResolveDatabase(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev); + void HandleResolveResource(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev); + void HandleResolve(TEvStateStorage::TEvBoardInfo::TPtr& ev); + STATEFN(StateResolveDatabase); + STATEFN(StateResolveResource); + void RedirectToDatabase(const TString& database); + bool NeedToRedirect(); + void HandleTimeout(); + void PassAway() override; }; } -} diff --git a/ydb/core/viewer/json_storage_base.h b/ydb/core/viewer/json_storage_base.h index f4f728369d08..1f8616ce6bbc 100644 --- a/ydb/core/viewer/json_storage_base.h +++ b/ydb/core/viewer/json_storage_base.h @@ -1,17 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "json_pipe_req.h" #include "viewer.h" +#include "viewer_bsgroupinfo.h" +#include "viewer_vdiskinfo.h" +#include "viewer_pdiskinfo.h" #include "viewer_helper.h" -#include "json_pipe_req.h" -#include "json_vdiskinfo.h" -#include "json_pdiskinfo.h" +#include "wb_merge.h" template<> struct std::hash { @@ -31,17 +25,16 @@ struct std::equal_to { } }; -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; using ::google::protobuf::FieldDescriptor; -class TJsonStorageBase : public TViewerPipeClient { +class TJsonStorageBase : public TViewerPipeClient { protected: - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; using TThis = TJsonStorageBase; using TNodeId = ui32; @@ -104,10 +97,10 @@ class TJsonStorageBase : public TViewerPipeClient { TString Erasure; ui32 Degraded; float Usage; - uint64 Used; - uint64 Limit; - uint64 Read; - uint64 Write; + ui64 Used; + ui64 Limit; + ui64 Read; + ui64 Write; TGroupRow() : Used(0) @@ -146,11 +139,7 @@ class TJsonStorageBase : public TViewerPipeClient { } public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - virtual void Bootstrap() { + void Bootstrap() override { TIntrusivePtr domains = AppData()->DomainsInfo; if (FilterTenant.empty()) { @@ -550,7 +539,7 @@ class TJsonStorageBase : public TViewerPipeClient { } } - virtual void ReplyAndPassAway() {} + void ReplyAndPassAway() override {} void HandleTimeout(TEvents::TEvWakeup::TPtr& ev) { switch (ev->Get()->Tag) { @@ -565,4 +554,3 @@ class TJsonStorageBase : public TViewerPipeClient { }; } -} diff --git a/ydb/core/viewer/json_vdisk_req.h b/ydb/core/viewer/json_vdisk_req.h index 43b455a2a11a..71ccc636ca7f 100644 --- a/ydb/core/viewer/json_vdisk_req.h +++ b/ydb/core/viewer/json_vdisk_req.h @@ -1,18 +1,12 @@ #pragma once -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" +#include "viewer.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; - template struct TJsonVDiskRequestHelper { static std::unique_ptr MakeRequest(NMon::TEvHttpInfo::TPtr &, TString *) { @@ -24,9 +18,8 @@ struct TJsonVDiskRequestHelper { } }; - template -class TJsonVDiskRequest : public TViewerPipeClient> { +class TJsonVDiskRequest : public TViewerPipeClient { enum EEv { EvRetryNodeRequest = EventSpaceBegin(NActors::TEvents::ES_PRIVATE), EvEnd @@ -42,7 +35,7 @@ class TJsonVDiskRequest : public TViewerPipeClient; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; using THelper = TJsonVDiskRequestHelper; IViewer* Viewer; TActorId Initiator; @@ -63,17 +56,13 @@ class TJsonVDiskRequest : public TViewerPipeClient TcpProxyId; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonVDiskRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Initiator(ev->Sender) , Event(ev) {} - virtual void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); NodeId = FromStringWithDefault(params.Get("node_id"), 0); PDiskId = FromStringWithDefault(params.Get("pdisk_id"), Max()); @@ -185,7 +174,7 @@ class TJsonVDiskRequest : public TViewerPipeClient -struct TJsonRequestParameters> { + void ReplyAndPassAway() override { + ReplyAndPassAway({}); + } + + static YAML::Node GetSchema() { + return TProtoToYaml::ProtoToYamlSchema(); + } + static YAML::Node GetParameters() { return YAML::Load(R"___( - name: node_id @@ -251,12 +245,4 @@ struct TJsonRequestParameters> { } }; -template -struct TJsonRequestSchema> { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -} } diff --git a/ydb/core/viewer/json_vdiskinfo.h b/ydb/core/viewer/json_vdiskinfo.h deleted file mode 100644 index 0675c4cd407b..000000000000 --- a/ydb/core/viewer/json_vdiskinfo.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "json_wb_req.h" - -namespace std { - -template <> -struct equal_to { - static decltype(auto) make_tuple(const NKikimrBlobStorage::TVDiskID& id) { - return std::make_tuple( - id.GetGroupID(), - id.GetGroupGeneration(), - id.GetRing(), - id.GetDomain(), - id.GetVDisk() - ); - } - - bool operator ()(const NKikimrBlobStorage::TVDiskID& a, const NKikimrBlobStorage::TVDiskID& b) const { - return make_tuple(a) == make_tuple(b); - } -}; - -template <> -struct less { - bool operator ()(const NKikimrBlobStorage::TVDiskID& a, const NKikimrBlobStorage::TVDiskID& b) const { - return equal_to::make_tuple(a) < equal_to::make_tuple(b); - } -}; - -template <> -struct hash { - size_t operator ()(const NKikimrBlobStorage::TVDiskID& a) const { - auto tp = equal_to::make_tuple(a); - return hash()(tp); - } -}; - -} - -namespace NKikimr { -namespace NViewer { - -template <> -struct TWhiteboardInfo { - using TResponseEventType = TEvWhiteboard::TEvVDiskStateResponse; - using TResponseType = NKikimrWhiteboard::TEvVDiskStateResponse; - using TElementType = NKikimrWhiteboard::TVDiskStateInfo; - using TElementKeyType = NKikimrBlobStorage::TVDiskID; - - static constexpr bool StaticNodesOnly = true; - - static ::google::protobuf::RepeatedPtrField& GetElementsField(TResponseType& response) { - return *response.MutableVDiskStateInfo(); - } - - static const NKikimrBlobStorage::TVDiskID& GetElementKey(const TElementType& type) { - return type.GetVDiskId(); - } - - static TString GetDefaultMergeField() { - return "VDiskId"; - } - - static void MergeResponses(TResponseType& result, TMap& responses, const TString& fields = GetDefaultMergeField()) { - if (fields == GetDefaultMergeField()) { - TWhiteboardMerger::MergeResponsesElementKey(result, responses); - } else { - TWhiteboardMerger::MergeResponses(result, responses, fields); - } - } -}; - -using TJsonVDiskInfo = TJsonWhiteboardRequest; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "VDisk information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "VDisk information"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_vdiskstat.h b/ydb/core/viewer/json_vdiskstat.h deleted file mode 100644 index 8aaa81ed1a9d..000000000000 --- a/ydb/core/viewer/json_vdiskstat.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "json_vdisk_req.h" - -namespace NKikimr { -namespace NViewer { - -using TJsonVDiskStat = TJsonVDiskRequest; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "VDisk statistic"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "VDisk statistic"; - } -}; - -} -} diff --git a/ydb/core/viewer/json_wb_req.cpp b/ydb/core/viewer/json_wb_req.cpp new file mode 100644 index 000000000000..facb6287f283 --- /dev/null +++ b/ydb/core/viewer/json_wb_req.cpp @@ -0,0 +1,231 @@ +#include "viewer_bsgroupinfo.h" +#include "viewer_nodeinfo.h" +#include "viewer_pdiskinfo.h" +#include "viewer_sysinfo.h" +#include "viewer_tabletinfo.h" +#include "viewer_vdiskinfo.h" +#include "json_handlers.h" + +namespace NKikimr::NViewer { + +YAML::Node GetWhiteboardRequestParameters() { + return YAML::Load(R"___( + - name: node_id + in: query + description: node identifier + required: false + type: integer + - name: merge + in: query + description: merge information from nodes + required: false + type: boolean + - name: group + in: query + description: group information by field + required: false + type: string + - name: all + in: query + description: return all possible key combinations (for enums only) + required: false + type: boolean + - name: filter + in: query + description: filter information by field + required: false + type: string + - name: alive + in: query + description: request from alive (connected) nodes only + required: false + type: boolean + - name: enums + in: query + description: convert enums to strings + required: false + type: boolean + - name: ui64 + in: query + description: return ui64 as number + required: false + type: boolean + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: retries + in: query + description: number of retries + required: false + type: integer + - name: retry_period + in: query + description: retry period in ms + required: false + type: integer + default: 500 + - name: static + in: query + description: request from static nodes only + required: false + type: boolean + - name: since + in: query + description: filter by update time + required: false + type: string + )___"); +} + +void InitViewerBSGroupInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Storage groups information", + .Description = "Returns information about storage groups" + }); + yaml.SetParameters(GetWhiteboardRequestParameters()); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/bsgroupinfo", new TJsonHandler(yaml)); + TWhiteboardInfo::InitMerger(); +} + +void InitViewerNodeInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Interconnect information", + .Description = "Returns information about node connections" + }); + yaml.SetParameters(GetWhiteboardRequestParameters()); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/nodeinfo", new TJsonHandler(yaml)); + TWhiteboardInfo::InitMerger(); +} + +void InitViewerPDiskInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "PDisk information", + .Description = "Returns information about PDisks" + }); + yaml.SetParameters(GetWhiteboardRequestParameters()); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/pdiskinfo", new TJsonHandler(yaml)); +} + +void InitViewerSysInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "System information", + .Description = "Returns system information" + }); + yaml.SetParameters(GetWhiteboardRequestParameters()); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/sysinfo", new TJsonHandler(yaml)); +} + +void InitViewerTabletInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Tablet information", + .Description = "Returns information about tablets" + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "node_id", + .Description = "node identifier", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "merge", + .Description = "merge information from nodes", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "group", + .Description = "group information by field", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "all", + .Description = "return all possible key combinations (for enums only)", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "filter", + .Description = "filter information by field", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "alive", + .Description = "request from alive (connected) nodes only", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "retries", + .Description = "number of retries", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "retry_period", + .Description = "retry period in ms", + .Type = "integer", + .Default = "500", + }); + yaml.AddParameter({ + .Name = "static", + .Description = "request from static nodes only", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "since", + .Description = "filter by update time", + .Type = "string", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/tabletinfo", new TJsonHandler(yaml)); +} + +void InitViewerVDiskInfoJsonHandler(TJsonHandlers& jsonHandlers) { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "VDisk information", + .Description = "Returns information about VDisks" + }); + yaml.SetParameters(GetWhiteboardRequestParameters()); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + jsonHandlers.AddHandler("/viewer/vdiskinfo", new TJsonHandler(yaml)); +} + +} diff --git a/ydb/core/viewer/json_wb_req.h b/ydb/core/viewer/json_wb_req.h index ae0905642b00..f5e0cd103336 100644 --- a/ydb/core/viewer/json_wb_req.h +++ b/ydb/core/viewer/json_wb_req.h @@ -1,48 +1,60 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" -#include "wb_merge.h" -#include "wb_group.h" +#include "log.h" +#include "viewer.h" #include "wb_filter.h" +#include "wb_group.h" +#include "wb_merge.h" #include "wb_req.h" -#include "log.h" +#include +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; +YAML::Node GetWhiteboardRequestParameters(); + template -class TJsonWhiteboardRequest : public TWhiteboardRequest, TRequestEventType, TResponseEventType> { -protected: +class TJsonWhiteboardRequest : public TWhiteboardRequest { +public: using TThis = TJsonWhiteboardRequest; - using TBase = TWhiteboardRequest; + using TBase = TWhiteboardRequest; using TResponseType = typename TResponseEventType::ProtoRecordType; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; + using TBase::Event; + using TBase::ReplyAndPassAway; TJsonSettings JsonSettings; -public: static constexpr NKikimrServices::TActivity::EType ActorActivityType() { return NKikimrServices::TActivity::VIEWER_HANDLER; } TJsonWhiteboardRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Event(ev) + : TBase(viewer, ev) {} void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); - SplitIds(params.Get("node_id"), ',', TBase::RequestSettings.FilterNodeIds); + std::vector nodeIds; + SplitIds(params.Get("node_id"), ',', nodeIds); + if (!nodeIds.empty()) { + if (TBase::RequestSettings.FilterNodeIds.empty()) { + TBase::RequestSettings.FilterNodeIds = nodeIds; + } else { + std::sort(nodeIds.begin(), nodeIds.end()); + std::sort(TBase::RequestSettings.FilterNodeIds.begin(), TBase::RequestSettings.FilterNodeIds.end()); + std::vector intersection; + std::set_intersection(nodeIds.begin(), nodeIds.end(), TBase::RequestSettings.FilterNodeIds.begin(), TBase::RequestSettings.FilterNodeIds.end(), std::back_inserter(intersection)); + if (intersection.empty()) { + TBase::RequestSettings.FilterNodeIds = {0}; + } else { + TBase::RequestSettings.FilterNodeIds = intersection; + } + } + } { TString merge = params.Get("merge"); if (merge.empty() || merge == "1" || merge == "true") { @@ -67,8 +79,14 @@ class TJsonWhiteboardRequest : public TWhiteboardRequest(params.Get("static"), false); } + if (params.Has("fields_required")) { + if (params.Get("fields_required") == "all") { + TBase::RequestSettings.FieldsRequired = {-1}; + } else { + SplitIds(params.Get("fields_required"), ',', TBase::RequestSettings.FieldsRequired); + } + } TBase::RequestSettings.Format = params.Get("format"); - TBase::Bootstrap(); } @@ -85,20 +103,23 @@ class TJsonWhiteboardRequest : public TWhiteboardRequestsecond; + auto it = TBase::NodeResponses.find(nodeId); + if (it != TBase::NodeResponses.end()) { + if (it->second.IsError()) { + if (error.empty()) { + error = it->second.GetError(); + } + errors++; } - errors++; } } } @@ -106,14 +127,14 @@ class TJsonWhiteboardRequest : public TWhiteboardRequestfirst << "\":"; @@ -123,94 +144,11 @@ class TJsonWhiteboardRequest : public TWhiteboardRequestSender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), std::move(json.Str())), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + ReplyAndPassAway(TBase::GetHTTPOKJSON(json.Str())); } catch (const std::exception& e) { - TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + ReplyAndPassAway(TBase::GetHTTPBADREQUEST("text/plain", e.what())); } - TBase::PassAway(); - } -}; - -template -struct TJsonRequestParameters> { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: node_id - in: query - description: node identifier - required: false - type: integer - - name: merge - in: query - description: merge information from nodes - required: false - type: boolean - - name: group - in: query - description: group information by field - required: false - type: string - - name: all - in: query - description: return all possible key combinations (for enums only) - required: false - type: boolean - - name: filter - in: query - description: filter information by field - required: false - type: string - - name: alive - in: query - description: request from alive (connected) nodes only - required: false - type: boolean - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: retries - in: query - description: number of retries - required: false - type: integer - - name: retry_period - in: query - description: retry period in ms - required: false - type: integer - default: 500 - - name: static - in: query - description: request from static nodes only - required: false - type: boolean - - name: since - in: query - description: filter by update time - required: false - type: string - )___"); } }; -template -struct TJsonRequestSchema> { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -} } diff --git a/ydb/core/viewer/log.h b/ydb/core/viewer/log.h index 8a0fe6dcf054..da5229ff0c68 100644 --- a/ydb/core/viewer/log.h +++ b/ydb/core/viewer/log.h @@ -1,16 +1,13 @@ #pragma once - -#include #include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { inline TString GetLogPrefix() { return {}; } -} } #define BLOG_D(stream) LOG_DEBUG_S(*TlsActivationContext, NKikimrServices::VIEWER, GetLogPrefix() << stream) diff --git a/ydb/core/viewer/operation_cancel.h b/ydb/core/viewer/operation_cancel.h index eea1a238bb88..2ed03faa9b77 100644 --- a/ydb/core/viewer/operation_cancel.h +++ b/ydb/core/viewer/operation_cancel.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "json_local_rpc.h" +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using TOperationCancelRpc = TJsonLocalRpcGet()->Request.GetMethod() != HTTP_METHOD_POST) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only POST method is allowed")); - } - - if (!PostToRequest()) { - return; - } - - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } - - if (Database.empty()) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("id")) { - Request.set_id(params.Get("id")); - } - - if (Request.id().empty()) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'id' is required")); - } + { + AllowedMethods = {HTTP_METHOD_POST}; + } - TBase::Bootstrap(); + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + post: + tags: + - operation + summary: Cancels operation + description: > + Starts cancellation of a long-running operation, + Clients can use GetOperation to check whether the cancellation succeeded + or whether the operation completed despite cancellation. + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: id + in: query + description: operation id + required: true + type: string + requestBody: + content: + application/json: + schema: {} + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + node["post"]["requestBody"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; } }; -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - post: - tags: - - operation - summary: Cancels operation - description: > - Starts cancellation of a long-running operation, - Clients can use GetOperation to check whether the cancellation succeeded - or whether the operation completed despite cancellation. - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: id - in: query - description: operation id - required: true - type: string - requestBody: - content: - application/json: - schema: {} - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - node["post"]["requestBody"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; } -} -} diff --git a/ydb/core/viewer/operation_forget.h b/ydb/core/viewer/operation_forget.h index b8338ae78409..330b03c88af4 100644 --- a/ydb/core/viewer/operation_forget.h +++ b/ydb/core/viewer/operation_forget.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "json_local_rpc.h" +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using TOperationForgetRpc = TJsonLocalRpcGet()->Request.GetMethod() != HTTP_METHOD_POST) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only POST method is allowed")); - } - - if (!PostToRequest()) { - return; - } - - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } - - if (Database.empty()) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("id")) { - Request.set_id(params.Get("id")); - } - - if (Request.id().empty()) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'id' is required")); - } + { + AllowedMethods = {HTTP_METHOD_POST}; + } - TBase::Bootstrap(); + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + post: + tags: + - operation + summary: Forgets operation + description: > + Forgets long-running operation. It does not cancel the operation and returns + an error if operation was not completed. + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: id + in: query + description: operation id + required: true + type: string + requestBody: + content: + application/json: + schema: {} + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + node["post"]["requestBody"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; } }; -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - post: - tags: - - operation - summary: Forgets operation - description: > - Forgets long-running operation. It does not cancel the operation and returns - an error if operation was not completed. - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: id - in: query - description: operation id - required: true - type: string - requestBody: - content: - application/json: - schema: {} - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - node["post"]["requestBody"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; -} - -} } diff --git a/ydb/core/viewer/operation_get.h b/ydb/core/viewer/operation_get.h index 5fd4a0b61f4c..af951b43443e 100644 --- a/ydb/core/viewer/operation_get.h +++ b/ydb/core/viewer/operation_get.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "json_local_rpc.h" +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using TOperationGetRpc = TJsonLocalRpcGet()->Request.GetMethod() != HTTP_METHOD_GET) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only GET method is allowed")); - } - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("id")) { - Request.set_id(params.Get("id")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'id' is required")); - } + { + AllowedMethods = {HTTP_METHOD_GET}; + } - TBase::Bootstrap(); + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - operation + summary: Get operation + description: Check status for a given operation + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: id + in: query + description: operation id + required: true + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; } }; -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - operation - summary: Get operation - description: Check status for a given operation - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: id - in: query - description: operation id - required: true - type: string - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; -} - -} } diff --git a/ydb/core/viewer/operation_list.h b/ydb/core/viewer/operation_list.h index e78b59188101..e72925d302de 100644 --- a/ydb/core/viewer/operation_list.h +++ b/ydb/core/viewer/operation_list.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "json_local_rpc.h" +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using TOperationListRpc = TJsonLocalRpcGet()->Request.GetMethod() != HTTP_METHOD_GET) { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only GET method is allowed")); - } - const auto& params(Event->Get()->Request.GetParams()); - if (params.Has("database")) { - Database = params.Get("database"); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'database' is required")); - } - - if (params.Has("kind")) { - Request.set_kind(params.Get("kind")); - } else { - return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'kind' is required")); - } - - if (params.Has("page_size")) { - Request.set_page_size(FromStringWithDefault(params.Get("page_size"), 0)); - } - - if (params.Has("page_token")) { - Request.set_page_token(params.Get("page_token")); - } + { + AllowedMethods = {HTTP_METHOD_GET}; + } - TBase::Bootstrap(); + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - operation + summary: List operations + description: Lists operations that match the specified filter in the request + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: kind + in: query + description: > + kind: + * `ss/backgrounds` + * `export` + * `import` + * `buildindex` + * `scriptexec` + required: true + type: string + - name: page_size + in: query + description: page size + required: false + type: integer + - name: page_token + in: query + description: page token + required: false + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + TProtoToYaml::FillEnum(node["get"]["parameters"][1]["enum"], NProtoBuf::GetEnumDescriptor(), { + .ConvertToLowerCase = true, + .SkipDefaultValue = true + }); + return node; } }; -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - operation - summary: List operations - description: Lists operations that match the specified filter in the request - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: kind - in: query - description: kind - required: true - type: string - - name: page_size - in: query - description: page size - required: false - type: integer - - name: page_token - in: query - description: page token - required: false - type: string - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - TProtoToYaml::FillEnum(node["get"]["parameters"][1]["enum"], NProtoBuf::GetEnumDescriptor(), { - .ConvertToLowerCase = true, - .SkipDefaultValue = true - }); - return node; -} - -} } diff --git a/ydb/core/viewer/pdisk_info.h b/ydb/core/viewer/pdisk_info.h index 0a1d1dba1092..78b105f6b891 100644 --- a/ydb/core/viewer/pdisk_info.h +++ b/ydb/core/viewer/pdisk_info.h @@ -1,21 +1,13 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" +#include "viewer.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TPDiskInfo : public TViewerPipeClient { +class TPDiskInfo : public TViewerPipeClient { enum EEv { EvRetryNodeRequest = EventSpaceBegin(NActors::TEvents::ES_PRIVATE), EvEnd @@ -30,7 +22,7 @@ class TPDiskInfo : public TViewerPipeClient { protected: using TThis = TPDiskInfo; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; ui32 Timeout = 0; ui32 ActualRetries = 0; ui32 Retries = 0; @@ -45,15 +37,11 @@ class TPDiskInfo : public TViewerPipeClient { ui32 PDiskId = 0; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TPDiskInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : TBase(viewer, ev) {} - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); NodeId = FromStringWithDefault(params.Get("node_id"), 0); PDiskId = FromStringWithDefault(params.Get("pdisk_id"), Max()); @@ -178,7 +166,7 @@ class TPDiskInfo : public TViewerPipeClient { TBase::PassAway(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { NKikimrViewer::TPDiskInfo proto; bool hasPDisk = false; bool hasVDisk = false; @@ -235,53 +223,51 @@ class TPDiskInfo : public TViewerPipeClient { }); TBase::ReplyAndPassAway(GetHTTPOKJSON(json.Str())); } -}; -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( - get: - tags: - - pdisk - summary: Gets PDisk info - description: Gets PDisk information from Whiteboard and BSC - parameters: - - name: node_id - in: query - description: node identifier - type: integer - - name: pdisk_id - in: query - description: pdisk identifier - required: true - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - YAML::Node properties(node["get"]["responses"]["200"]["content"]["application/json"]["schema"]["properties"]["BSC"]["properties"]); - TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["StatusV2"], NProtoBuf::GetEnumDescriptor()); - TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["DecommitStatus"], NProtoBuf::GetEnumDescriptor()); - TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["Type"], NProtoBuf::GetEnumDescriptor()); - TProtoToYaml::FillEnum(properties["VDisks"]["items"]["properties"]["StatusV2"], NProtoBuf::GetEnumDescriptor()); - return node; -} + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - pdisk + summary: Gets PDisk info + description: Gets PDisk information from Whiteboard and BSC + parameters: + - name: node_id + in: query + description: node identifier + type: integer + - name: pdisk_id + in: query + description: pdisk identifier + required: true + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + YAML::Node properties(node["get"]["responses"]["200"]["content"]["application/json"]["schema"]["properties"]["BSC"]["properties"]); + TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["StatusV2"], NProtoBuf::GetEnumDescriptor()); + TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["DecommitStatus"], NProtoBuf::GetEnumDescriptor()); + TProtoToYaml::FillEnum(properties["PDisk"]["properties"]["Type"], NProtoBuf::GetEnumDescriptor()); + TProtoToYaml::FillEnum(properties["VDisks"]["items"]["properties"]["StatusV2"], NProtoBuf::GetEnumDescriptor()); + return node; + } +}; } -} diff --git a/ydb/core/viewer/json_pdisk_restart.h b/ydb/core/viewer/pdisk_restart.h similarity index 69% rename from ydb/core/viewer/json_pdisk_restart.h rename to ydb/core/viewer/pdisk_restart.h index 98d499466eb2..440a4429a082 100644 --- a/ydb/core/viewer/json_pdisk_restart.h +++ b/ydb/core/viewer/pdisk_restart.h @@ -1,20 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" +#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonPDiskRestart : public TViewerPipeClient { +class TJsonPDiskRestart : public TViewerPipeClient { enum EEv { EvRetryNodeRequest = EventSpaceBegin(NActors::TEvents::ES_PRIVATE), EvEnd @@ -29,7 +23,7 @@ class TJsonPDiskRestart : public TViewerPipeClient { protected: using TThis = TJsonPDiskRestart; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; IViewer* Viewer; NMon::TEvHttpInfo::TPtr Event; ui32 Timeout = 0; @@ -44,16 +38,12 @@ class TJsonPDiskRestart : public TViewerPipeClient { bool Force = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonPDiskRestart(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Event(ev) {} - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); NodeId = FromStringWithDefault(params.Get("node_id"), 0); PDiskId = FromStringWithDefault(params.Get("pdisk_id"), Max()); @@ -139,7 +129,7 @@ class TJsonPDiskRestart : public TViewerPipeClient { TBase::PassAway(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { NJson::TJsonValue json; if (Response != nullptr) { if (Response->Record.GetResponse().GetSuccess()) { @@ -165,61 +155,60 @@ class TJsonPDiskRestart : public TViewerPipeClient { } PassAway(); } -}; -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - return YAML::Load(R"___( - post: - tags: - - pdisk - summary: Restart PDisk - description: Restart PDisk on the specified node - parameters: - - name: node_id - in: query - description: node identifier - type: integer - - name: pdisk_id - in: query - description: pdisk identifier - required: true - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: force - in: query - description: attempt forced operation, ignore warnings - required: false - type: boolean - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - properties: - result: - type: boolean - description: was operation successful or not - error: - type: string - description: details about failed operation - forceRetryPossible: - type: boolean - description: if true, operation can be retried with force flag - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); -} + static YAML::Node GetSwagger() { + return YAML::Load(R"___( + post: + tags: + - pdisk + summary: Restart PDisk + description: Restart PDisk on the specified node + parameters: + - name: node_id + in: query + description: node identifier + type: integer + - name: pdisk_id + in: query + description: pdisk identifier + required: true + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: force + in: query + description: attempt forced operation, ignore warnings + required: false + type: boolean + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: boolean + description: was operation successful or not + error: + type: string + description: details about failed operation + forceRetryPossible: + type: boolean + description: if true, operation can be retried with force flag + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + } + +}; -} } diff --git a/ydb/core/viewer/pdisk_status.h b/ydb/core/viewer/pdisk_status.h index 2ac122008ca0..a8330716ef46 100644 --- a/ydb/core/viewer/pdisk_status.h +++ b/ydb/core/viewer/pdisk_status.h @@ -1,25 +1,18 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" +#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; extern bool IsPostContent(const NMon::TEvHttpInfo::TPtr& event); -class TPDiskStatus : public TViewerPipeClient { +class TPDiskStatus : public TViewerPipeClient { protected: using TThis = TPDiskStatus; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; IViewer* Viewer; NMon::TEvHttpInfo::TPtr Event; ui32 Timeout = 0; @@ -30,16 +23,12 @@ class TPDiskStatus : public TViewerPipeClient { bool Force = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TPDiskStatus(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Event(ev) {} - void Bootstrap() { + void Bootstrap() override { if (Event->Get()->Request.GetMethod() != HTTP_METHOD_POST) { TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes( Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only POST method is allowed"), @@ -132,7 +121,7 @@ class TPDiskStatus : public TViewerPipeClient { TBase::PassAway(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { NJson::TJsonValue json; if (Response != nullptr) { if (Response->Record.GetResponse().GetSuccess()) { @@ -158,89 +147,87 @@ class TPDiskStatus : public TViewerPipeClient { } PassAway(); } -}; -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( post: - tags: - - pdisk - summary: Updates PDisk status - description: Updates PDisk status in BSC - parameters: - - name: node_id - in: query - description: node identifier - type: integer - - name: pdisk_id - in: query - description: pdisk identifier - required: true - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: force - in: query - description: attempt forced operation, ignore warnings - required: false - type: boolean - requestBody: - description: Updates of PDisk statuses - required: true - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: [ACTIVE, INACTIVE, BROKEN, FAULTY, TO_BE_REMOVED, UNKNOWN] - description: > - PDisk operational status: - * `ACTIVE` - working as expected - * `INACTIVE` - new groups are not created using this drive, but existing ones continue to work as expected - * `BROKEN` - drive is marked as not working, groups will be immediately moved out of this drive upon receiving this status - * `FAULTY` - drive is expected to become BROKEN soon, new groups are not created, old groups are asynchronously moved out from this drive - * `TO_BE_REMOVED` - same as INACTIVE, but drive is counted in fault model as not working - decommit_status: - type: string - enum: [DECOMMIT_NONE, DECOMMIT_PENDING, DECOMMIT_IMMINENT, DECOMMIT_REJECTED] - description: > - PDisk decommission status: - * `DECOMMIT_NONE` - disk is not in decomission state - * `DECOMMIT_PENDING` - decomission is planned for this disk, but not started yet. existing slots are not moved from the disk, but no new slots are allocated on it - * `DECOMMIT_IMMINENT` - decomission has started for this disk. existing slots are moved from the disk - * `DECOMMIT_REJECTED` - no slots from other disks are placed on this disk in the process of decommission - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - properties: - result: - type: boolean - description: was operation successful or not - error: - type: string - description: details about failed operation - forceRetryPossible: - type: boolean - description: if true, operation can be retried with force flag - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - return node; -} + tags: + - pdisk + summary: Updates PDisk status + description: Updates PDisk status in BSC + parameters: + - name: node_id + in: query + description: node identifier + type: integer + - name: pdisk_id + in: query + description: pdisk identifier + required: true + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: force + in: query + description: attempt forced operation, ignore warnings + required: false + type: boolean + requestBody: + description: Updates of PDisk statuses + required: true + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [ACTIVE, INACTIVE, BROKEN, FAULTY, TO_BE_REMOVED, UNKNOWN] + description: > + PDisk operational status: + * `ACTIVE` - working as expected + * `INACTIVE` - new groups are not created using this drive, but existing ones continue to work as expected + * `BROKEN` - drive is marked as not working, groups will be immediately moved out of this drive upon receiving this status + * `FAULTY` - drive is expected to become BROKEN soon, new groups are not created, old groups are asynchronously moved out from this drive + * `TO_BE_REMOVED` - same as INACTIVE, but drive is counted in fault model as not working + decommit_status: + type: string + enum: [DECOMMIT_NONE, DECOMMIT_PENDING, DECOMMIT_IMMINENT, DECOMMIT_REJECTED] + description: > + PDisk decommission status: + * `DECOMMIT_NONE` - disk is not in decomission state + * `DECOMMIT_PENDING` - decomission is planned for this disk, but not started yet. existing slots are not moved from the disk, but no new slots are allocated on it + * `DECOMMIT_IMMINENT` - decomission has started for this disk. existing slots are moved from the disk + * `DECOMMIT_REJECTED` - no slots from other disks are placed on this disk in the process of decommission + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: boolean + description: was operation successful or not + error: + type: string + description: details about failed operation + forceRetryPossible: + type: boolean + description: if true, operation can be retried with force flag + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + return node; + } +}; } -} diff --git a/ydb/core/viewer/protos/viewer.proto b/ydb/core/viewer/protos/viewer.proto index fba41698e9ed..a88f66663007 100644 --- a/ydb/core/viewer/protos/viewer.proto +++ b/ydb/core/viewer/protos/viewer.proto @@ -93,6 +93,7 @@ message TMetaCommonInfo { repeated uint32 Nodes = 15; repeated uint32 Disks = 16; repeated TACE ACL = 17; + bool InterruptInheritance = 40; TBackupInfo Backup = 18; TResources Resources = 19; @@ -313,23 +314,33 @@ enum EFlag { } message TClusterInfo { - string Name = 1; - EFlag Overall = 2; + uint32 Version = 1; + string Name = 2; + string Domain = 3; + EFlag Overall = 5; + repeated string Problems = 9; uint32 NodesTotal = 10; uint32 NodesAlive = 11; uint32 NumberOfCpus = 20; - double LoadAverage = 21; + double CoresUsed = 21; + double LoadAverage = 22; + repeated NKikimrWhiteboard.TSystemStateInfo.TPoolStats PoolStats = 23; uint64 MemoryTotal = 30; uint64 MemoryUsed = 31; uint64 StorageTotal = 40; uint64 StorageUsed = 41; - repeated string DataCenters = 42; - repeated string Versions = 43; - repeated NKikimrWhiteboard.TTabletStateInfo SystemTablets = 16; - uint64 Hosts = 44; - uint64 Tenants = 45; - uint64 Tablets = 46; - string Domain = 47; + map MapStorageTotal = 42; + map MapStorageUsed = 43; + repeated string DataCenters = 44; + map MapDataCenters = 45; + repeated string Versions = 46; + map MapVersions = 47; + map MapNodeStates = 48; + map MapNodeRoles = 49; + repeated NKikimrWhiteboard.TTabletStateInfo SystemTablets = 50; + repeated NKikimrSysView.TStorageStatsEntry StorageStats = 51; + uint64 Hosts = 60; + uint64 Tenants = 61; } enum ETenantType { @@ -381,7 +392,8 @@ message TTenant { uint64 StorageGroups = 40; uint64 StorageAllocatedLimit = 41; Ydb.Cms.DatabaseQuotas DatabaseQuotas = 42; - repeated TStorageUsage StorageUsage = 43; + repeated TStorageUsage TablesStorage = 44; + repeated TStorageUsage DatabaseStorage = 45; } message TTenants { @@ -391,10 +403,76 @@ message TTenants { message TTenantInfo { repeated TTenant TenantInfo = 1; repeated string Errors = 2; + uint32 Version = 3; +} + +message TStoragePDisk { + string PDiskId = 1; + string Path = 2; + string Type = 3; + string Guid = 4; + uint64 Category = 5; + uint64 TotalSize = 6; + uint64 AvailableSize = 7; + string Status = 8; + EFlag DiskSpace = 9; + string DecommitStatus = 10; + uint64 SlotSize = 11; + NKikimrWhiteboard.TPDiskStateInfo Whiteboard = 50; +} + +message TStorageVDisk { + string VDiskId = 1; + uint32 NodeId = 2; + uint64 AllocatedSize = 3; + uint64 AvailableSize = 4; + string Kind = 5; + string Status = 6; + EFlag DiskSpace = 7; + repeated TStorageVDisk Donors = 8; + TStoragePDisk PDisk = 20; + NKikimrWhiteboard.TVDiskStateInfo Whiteboard = 50; } message TStorageGroupInfo { string GroupId = 1; + uint64 GroupGeneration = 2; + string PoolName = 3; + bool Encryption = 4; + EFlag Overall = 5; + EFlag DiskSpace = 6; + string Kind = 7; + string MediaType = 8; + string ErasureSpecies = 9; + optional uint64 AllocationUnits = 10; + string State = 11; + optional uint64 MissingDisks = 12; // Degraded + optional uint64 Used = 13; + optional uint64 Limit = 14; + optional uint64 Available = 15; + optional float Usage = 16; + optional uint64 Read = 17; + optional uint64 Write = 18; + optional float DiskSpaceUsage = 19; + optional uint64 LatencyPutTabletLog = 20; + optional uint64 LatencyPutUserData = 21; + optional uint64 LatencyGetFast = 22; + repeated TStorageVDisk VDisks = 30; +} + +message TStorageGroupGroup { + string GroupName = 1; + uint64 GroupCount = 2; + optional uint64 Used = 13; + optional uint64 Limit = 14; + optional uint64 Available = 15; + optional float Usage = 16; + optional uint64 Read = 17; + optional uint64 Write = 18; + optional float DiskSpaceUsage = 19; + optional uint64 LatencyPutTabletLog = 20; + optional uint64 LatencyPutUserData = 21; + optional uint64 LatencyGetFast = 22; } message TStoragePoolInfo { @@ -420,27 +498,61 @@ message TStorageInfo { repeated TStorageGroupInfo StorageGroups = 5; } +message TStorageGroupsInfo { + uint32 Version = 1; + optional uint32 TotalGroups = 2; + optional uint32 FoundGroups = 3; + optional string FieldsAvailable = 4; + optional string FieldsRequired = 5; + optional bool NeedFilter = 6; + optional bool NeedGroup = 7; + optional bool NeedSort = 8; + optional bool NeedLimit = 9; + repeated string Problems = 10; + repeated TStorageGroupInfo StorageGroups = 11; + repeated TStorageGroupGroup StorageGroupGroups = 12; +} + message TStorageUsageStats { uint32 Pace = 1; repeated uint32 Buckets = 2; } +message TNodeGroup { + string GroupName = 1; + uint64 NodeCount = 2; +} + message TNodeInfo { uint32 NodeId = 1; - NKikimrWhiteboard.TSystemStateInfo SystemState = 2; - repeated NKikimrWhiteboard.TPDiskStateInfo PDisks = 3; - repeated NKikimrWhiteboard.TVDiskStateInfo VDisks = 4; - repeated TTabletStateInfo Tablets = 5; + string Database = 2; + int32 UptimeSeconds = 3; // negative for disconnect time + bool Disconnected = 4; + float CpuUsage = 5; + float DiskSpaceUsage = 6; + NKikimrWhiteboard.TSystemStateInfo SystemState = 10; + repeated NKikimrWhiteboard.TPDiskStateInfo PDisks = 20; + repeated NKikimrWhiteboard.TVDiskStateInfo VDisks = 30; + repeated TTabletStateInfo Tablets = 40; } message TNodesInfo { - EFlag Overall = 1; - repeated TNodeInfo Nodes = 2; - optional uint64 TotalNodes = 3; - optional uint64 FoundNodes = 4; - optional uint64 MaximumDisksPerNode = 5; - optional bool NoDC = 6; - optional bool NoRack = 7; + uint32 Version = 1; + optional uint64 TotalNodes = 2; + optional uint64 FoundNodes = 3; + optional string FieldsAvailable = 4; + optional string FieldsRequired = 5; + optional bool NeedFilter = 6; + optional bool NeedGroup = 7; + optional bool NeedSort = 8; + optional bool NeedLimit = 9; + repeated string Problems = 10; + repeated TNodeInfo Nodes = 20; + repeated TNodeGroup NodeGroups = 30; + optional uint64 MaximumDisksPerNode = 50; + optional uint64 MaximumSlotsPerDisk = 51; + optional bool NoDC = 60; + optional bool NoRack = 61; } enum ENodeType { @@ -547,15 +659,20 @@ message TSchemeCacheRequest { message TEvViewerRequest { TNodeLocation Location = 1; uint32 Timeout = 2; // ms + string MergeFields = 3; oneof Request { NKikimrWhiteboard.TEvTabletStateRequest TabletRequest = 11; NKikimrWhiteboard.TEvSystemStateRequest SystemRequest = 12; + NKikimrWhiteboard.TEvVDiskStateRequest VDiskRequest = 16; + NKikimrWhiteboard.TEvPDiskStateRequest PDiskRequest = 17; + NKikimrWhiteboard.TEvBSGroupStateRequest BSGroupRequest = 18; THttpProxyRequest QueryRequest = 13; THttpProxyRequest RenderRequest = 14; TSchemeCacheRequest AutocompleteRequest = 15; - bytes Reserved16 = 16; - bytes Reserved17 = 17; - bytes Reserved18 = 18; + bytes Reserved19 = 19; + bytes Reserved20 = 20; + bytes Reserved21 = 21; + bytes Reserved22 = 22; } } @@ -564,12 +681,16 @@ message TEvViewerResponse { oneof Response { NKikimrWhiteboard.TEvTabletStateResponse TabletResponse = 11; NKikimrWhiteboard.TEvSystemStateResponse SystemResponse = 12; + NKikimrWhiteboard.TEvVDiskStateResponse VDiskResponse = 16; + NKikimrWhiteboard.TEvPDiskStateResponse PDiskResponse = 17; + NKikimrWhiteboard.TEvBSGroupStateResponse BSGroupResponse = 18; NKikimrKqp.TEvQueryResponse QueryResponse = 13; NKikimrGraph.TEvMetricsResult RenderResponse = 14; TQueryAutocomplete AutocompleteResponse = 15; - bytes Reserved16 = 16; - bytes Reserved17 = 17; - bytes Reserved18 = 18; + bytes Reserved19 = 19; + bytes Reserved20 = 20; + bytes Reserved21 = 21; + bytes Reserved22 = 22; } } @@ -580,18 +701,17 @@ message TEvDescribeSchemeInfo { Cache = 2; } - optional string Status = 1; - optional string Reason = 2; - optional string Path = 3; - optional NKikimrSchemeOp.TPathDescription PathDescription = 4; - optional fixed64 DEPRECATED_PathOwner = 5; // replaced by PathOwnerId - optional fixed64 PathId = 6; - - optional string LastExistedPrefixPath = 7; - optional fixed64 LastExistedPrefixPathId = 8; - optional NKikimrSchemeOp.TPathDescription LastExistedPrefixDescription = 9; - optional fixed64 PathOwnerId = 10; - optional ESource Source = 11; + string Status = 1; + string Reason = 2; + string Path = 3; + NKikimrSchemeOp.TPathDescription PathDescription = 4; + fixed64 DEPRECATED_PathOwner = 5; // replaced by PathOwnerId + fixed64 PathId = 6; + string LastExistedPrefixPath = 7; + fixed64 LastExistedPrefixPathId = 8; + NKikimrSchemeOp.TPathDescription LastExistedPrefixDescription = 9; + fixed64 PathOwnerId = 10; + ESource Source = 11; } enum EAutocompleteType { @@ -627,13 +747,14 @@ message TQueryAutocomplete { EAutocompleteType Type = 2; string Parent = 3; } - uint32 Total = 1; + optional uint32 Total = 1; repeated TEntity Entities = 2; } bool Success = 1; TResult Result = 2; repeated string Error = 3; + uint32 Version = 4; } message TPDiskInfoWhiteboard { @@ -650,3 +771,19 @@ message TPDiskInfo { TPDiskInfoWhiteboard Whiteboard = 1; TPDiskInfoBSC BSC = 2; } + +message TFeatureFlagsConfig { + message TFeatureFlag { + string Name = 1; + optional bool Current = 2; + optional bool Default = 3; + } + + message TDatabase { + string Name = 1; + repeated TFeatureFlag FeatureFlags = 2; + } + + uint32 Version = 1; + repeated TDatabase Databases = 2; +} diff --git a/ydb/core/viewer/query_autocomplete_helper.h b/ydb/core/viewer/query_autocomplete_helper.h index e523ceb029bb..b9da60290800 100644 --- a/ydb/core/viewer/query_autocomplete_helper.h +++ b/ydb/core/viewer/query_autocomplete_helper.h @@ -1,6 +1,8 @@ #pragma once - +#include #include +#include +#include namespace NKikimr::NViewer { @@ -31,89 +33,35 @@ inline ui32 LevenshteinDistance(TString word1, TString word2) { return dist[size1][size2]; } -template class FuzzySearcher { - struct WordHit { - bool Contains; - ui32 LengthDifference; - ui32 LevenshteinDistance; - Type Data; - - WordHit(bool contains, ui32 lengthDifference, ui32 levenshteinDistance, Type data) - : Contains(contains) - , LengthDifference(lengthDifference) - , LevenshteinDistance(levenshteinDistance) - , Data(data) - {} - - bool operator<(const WordHit& other) const { - if (this->Contains && !other.Contains) { - return true; - } - if (this->Contains && other.Contains) { - return this->LengthDifference < other.LengthDifference; - } - return this->LevenshteinDistance < other.LevenshteinDistance; - } - - bool operator>(const WordHit& other) const { - if (!this->Contains && other.Contains) { - return true; - } - if (this->Contains && other.Contains) { - return this->LengthDifference > other.LengthDifference; - } - return this->LevenshteinDistance > other.LevenshteinDistance; - } - }; - - static WordHit CalculateWordHit(TString searchWord, TString testWord, Type testData) { - searchWord = to_lower(searchWord); - testWord = to_lower(testWord); - if (testWord.Contains(searchWord)) { - return {1, static_cast(testWord.length() - searchWord.length()), 0, testData}; + static size_t CalculateWordHit(const TString& searchWord, const TString& testWord) { + size_t findPos = testWord.find(searchWord); + if (findPos != TString::npos) { + return testWord.size() - searchWord.size() + findPos; } else { - ui32 levenshteinDistance = LevenshteinDistance(searchWord, testWord); - return {0, 0, levenshteinDistance, testData}; + return 1000 * LevenshteinDistance(searchWord, testWord); } } public: - THashMap Dictionary; - - FuzzySearcher(const THashMap& dictionary) - : Dictionary(dictionary) {} - - FuzzySearcher(const TVector& words) { - for (const auto& word : words) { - Dictionary[word] = word; + template + static std::vector Search(const std::vector& dictionary, const TString& searchWord, ui32 limit = 10) { + TString search = to_lower(searchWord); + std::vector> hits; // {distance, index} + hits.reserve(dictionary.size()); + for (size_t index = 0; index < dictionary.size(); ++index) { + hits.emplace_back(CalculateWordHit(search, to_lower(TString(dictionary[index]))), index); } - } - - TVector Search(const TString& searchWord, ui32 limit = 10) { - auto cmp = [](const WordHit& left, const WordHit& right) { - return left < right; - }; - std::priority_queue, decltype(cmp)> queue(cmp); - - for (const auto& [word, data]: Dictionary) { - auto wordHit = CalculateWordHit(searchWord, word, data); - if (queue.size() < limit) { - queue.emplace(wordHit); - } else if (queue.size() > 0 && wordHit < queue.top()) { - queue.pop(); - queue.emplace(wordHit); - } + std::sort(hits.begin(), hits.end()); + if (hits.size() > limit) { + hits.resize(limit); } - - TVector results; - while (!queue.empty()) { - results.emplace_back(queue.top().Data); - queue.pop(); + std::vector result; + result.reserve(hits.size()); + for (const auto& hit : hits) { + result.emplace_back(&dictionary[hit.second]); } - - std::reverse(results.begin(), results.end()); - return results; + return result; } }; diff --git a/ydb/core/viewer/query_execute_script.h b/ydb/core/viewer/query_execute_script.h new file mode 100644 index 000000000000..96f0fb09907a --- /dev/null +++ b/ydb/core/viewer/query_execute_script.h @@ -0,0 +1,96 @@ +#pragma once +#include "json_local_rpc.h" +#include +#include +#include +#include + +namespace NKikimr { + +namespace NRpcService { + +template<> +void SetRequestSyncOperationMode(Ydb::Query::ExecuteScriptRequest& request) { + request.mutable_operation_params()->set_operation_mode(Ydb::Operations::OperationParams::ASYNC); +} + +} + +namespace NViewer { + +using TQueryExecuteScriptRpc = TJsonLocalRpc>; + +class TQueryExecuteScript : public TQueryExecuteScriptRpc { +public: + using TBase = TQueryExecuteScriptRpc; + + TQueryExecuteScript(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + { + AllowedMethods = {HTTP_METHOD_POST}; + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + post: + tags: + - script query + summary: Execute script + description: Execute script + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + database: + type: string + required: true + script_content: + type: object + properties: + text: + type: string + description: query text + required: true + syntax: + type: string + description: | + syntax: + * `SYNTAX_YQL_V1` + * `SYNTAX_PG` + required: false + exec_mode: + type: string + description: | + exec_mode: + * `EXEC_MODE_PARSE` + * `EXEC_MODE_VALIDATE` + * `EXEC_MODE_EXPLAIN` + * `EXEC_MODE_EXECUTE` + required: true + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; + } +}; + +} +} diff --git a/ydb/core/viewer/query_fetch_script.h b/ydb/core/viewer/query_fetch_script.h new file mode 100644 index 000000000000..6a64d230b00f --- /dev/null +++ b/ydb/core/viewer/query_fetch_script.h @@ -0,0 +1,76 @@ +#pragma once +#include "json_local_rpc.h" +#include +#include +#include + +namespace NKikimr::NViewer { + +using TQueryFetchScriptRpc = TJsonLocalRpc>; + +class TQueryFetchScript : public TQueryFetchScriptRpc { +public: + using TBase = TQueryFetchScriptRpc; + + TQueryFetchScript(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + { + AllowedMethods = {HTTP_METHOD_GET}; + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - script query + summary: Get operation + description: Check status for a given operation + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: operation_id + in: query + description: operation id + required: true + type: string + - name: result_set_index + in: query + description: result set index + required: false + type: string + - name: fetch_token + in: query + description: fetch token + required: false + type: string + - name: rows_limit + in: query + description: rows limit (less than 1000 allowed) + required: false + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; + } +}; + +} diff --git a/ydb/core/viewer/scheme_directory.h b/ydb/core/viewer/scheme_directory.h index 400504fbd822..d483199036eb 100644 --- a/ydb/core/viewer/scheme_directory.h +++ b/ydb/core/viewer/scheme_directory.h @@ -1,11 +1,10 @@ #pragma once -#include -#include -#include "json_local_rpc.h" #include "json_handlers.h" +#include "json_local_rpc.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { class TSchemeDirectory : public IActor { public: @@ -33,39 +32,37 @@ using TSchemeDirectoryDeleteRpc = TJsonLocalRpc class TSchemeDirectoryRequest : public LocalRpcType { -public: +protected: using TBase = LocalRpcType; + using TRequestProtoType = typename TBase::TRequestProtoType; + using TBase::Database; +public: TSchemeDirectoryRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : TBase(viewer, ev) {} - void Bootstrap() override { - if (!TBase::PostToRequest()) { - return; - } - - const auto& params(TBase::Event->Get()->Request.GetParams()); - if (params.Has("database")) { - TBase::Database = params.Get("database"); - } - if (TBase::Database.empty()) { - return TBase::ReplyAndPassAway(TBase::Viewer->GetHTTPBADREQUEST(TBase::Event->Get(), "text/plain", "field 'database' is required")); + bool ValidateRequest(TRequestProtoType& request) override { + if (TBase::ValidateRequest(request)) { + if (Database && request.path()) { + TString path = request.path(); + if (!path.empty() && path[0] != '/') { + path = Database + "/" + path; + request.set_path(path); + } + } + return true; } - - if (params.Has("path")) { - TBase::Request.set_path(params.Get("path")); - } - if (TBase::Request.path().empty()) { - return TBase::ReplyAndPassAway(TBase::Viewer->GetHTTPBADREQUEST(TBase::Event->Get(), "text/plain", "field 'path' is required")); - } - - TBase::Bootstrap(); + return false; } }; class TJsonSchemeDirectoryHandler : public TJsonHandler { public: + TJsonSchemeDirectoryHandler() + : TJsonHandler(GetSwagger()) + {} + IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) override { switch (event->Get()->Request.GetMethod()) { case HTTP_METHOD_GET: @@ -78,102 +75,100 @@ class TJsonSchemeDirectoryHandler : public TJsonHandler { throw std::logic_error("Bad request method"); } } -}; -template<> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( get: - tags: - - scheme - summary: List directory - description: Returns information about given directory and objects inside it - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: path - in: query - description: path to directory - required: true - type: string - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout + tags: + - scheme + summary: List directory + description: Returns information about given directory and objects inside it + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: path + in: query + description: path to directory + required: true + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout post: - tags: - - scheme - summary: Make directory - description: Makes directory - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: path - in: query - description: path to directory - required: true - type: string - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout + tags: + - scheme + summary: Make directory + description: Makes directory + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: path + in: query + description: path to directory + required: true + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout delete: - tags: - - scheme - summary: Remove directory - description: Removes directory - parameters: - - name: database - in: query - description: database name - required: true - type: string - - name: path - in: query - description: path to directory - required: true - type: string - responses: - 200: - description: OK - content: - application/json: - schema: {} - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - - node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - node["delete"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); - return node; -} + tags: + - scheme + summary: Remove directory + description: Removes directory + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: path + in: query + description: path to directory + required: true + type: string + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + node["post"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + node["delete"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; + } +}; } -} diff --git a/ydb/core/viewer/storage_groups.h b/ydb/core/viewer/storage_groups.h new file mode 100644 index 000000000000..e18a86a680b5 --- /dev/null +++ b/ydb/core/viewer/storage_groups.h @@ -0,0 +1,2390 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" +#include "viewer_helper.h" +#include +#include + +namespace NKikimr::NViewer { + +using namespace NProtobufJson; + +using TNodeId = ui32; +using TGroupId = ui32; + +struct TPDiskId { + TNodeId NodeId; + ui32 PDiskId; + + TPDiskId() = default; + + TPDiskId(TNodeId nodeId, ui32 pdiskId) + : NodeId(nodeId) + , PDiskId(pdiskId) + {} + + TPDiskId(const NKikimrSysView::TPDiskKey& key) + : NodeId(key.GetNodeId()) + , PDiskId(key.GetPDiskId()) + {} + + TPDiskId(const NKikimrSysView::TVSlotKey& key) + : NodeId(key.GetNodeId()) + , PDiskId(key.GetPDiskId()) + {} + + bool operator ==(const TPDiskId& a) const = default; +}; + +struct TVSlotId : TPDiskId { + ui32 VDiskSlotId; + + TVSlotId() = default; + + TVSlotId(TNodeId nodeId, ui32 pdiskId, ui32 vdiskSlotId) + : TPDiskId(nodeId, pdiskId) + , VDiskSlotId(vdiskSlotId) + {} + + TVSlotId(const NKikimrBlobStorage::TVSlotId& proto) + : TVSlotId(proto.GetNodeId(), proto.GetPDiskId(), proto.GetVSlotId()) + {} + + TVSlotId(const NKikimrSysView::TVSlotKey& proto) + : TVSlotId(proto.GetNodeId(), proto.GetPDiskId(), proto.GetVSlotId()) + {} + + TVSlotId(const NKikimrWhiteboard::TVDiskStateInfo& proto) + : TVSlotId(proto.GetNodeId(), proto.GetPDiskId(), proto.GetVDiskSlotId()) + {} + + bool operator ==(const TVSlotId& a) const = default; +}; + +} + +template<> +struct std::hash { + std::size_t operator ()(const NKikimr::NViewer::TPDiskId& s) const { + return ::std::hash()(s.NodeId) + ^ (::std::hash()(s.PDiskId) << 1); + } +}; + +template<> +struct std::hash { + std::size_t operator ()(const NKikimr::NViewer::TVSlotId& s) const { + return ::std::hash()(s.NodeId) + ^ (::std::hash()(s.PDiskId) << 1) + ^ (::std::hash()(s.VDiskSlotId) << 2); + } +}; + +namespace NKikimr::NViewer { + +using namespace NActors; +using namespace NNodeWhiteboard; + +enum class EGroupFields : ui8 { + GroupId, + PoolName, + Kind, + MediaType, + Erasure, + MissingDisks, + State, + Usage, + Encryption, + Used, + Limit, + Read, + Write, + Available, + AllocationUnits, + DiskSpaceUsage, + NodeId, + PDiskId, + VDisk, // VDisk information + PDisk, // PDisk information + Latency, + COUNT +}; + +constexpr ui8 operator +(EGroupFields e) { + return static_cast(e); +} + +class TStorageGroups : public TViewerPipeClient { +public: + using TBase = TViewerPipeClient; + using TThis = TStorageGroups; + using TFieldsType = std::bitset<+EGroupFields::COUNT>; + + // Common + std::unordered_map> NavigateKeySetResult; + ui64 NavigateKeySetInFlight = 0; + std::unordered_map PathId2HiveId; + std::unordered_map> HiveStorageStats; + ui64 HiveStorageStatsInFlight = 0; + + // BSC + bool FallbackToWhiteboard = false; + std::optional> GetGroupsResponse; + std::optional> GetStoragePoolsResponse; + std::optional> GetVSlotsResponse; + std::optional> GetPDisksResponse; + + // Whiteboard + std::optional> NodesInfo; + std::unordered_map> BSGroupStateResponse; + ui64 BSGroupStateRequestsInFlight = 0; + std::unordered_map> VDiskStateResponse; + ui64 VDiskStateRequestsInFlight = 0; + std::unordered_map> PDiskStateResponse; + ui64 PDiskStateRequestsInFlight = 0; + + ui32 Timeout = 0; + TString Filter; + std::unordered_set DatabaseStoragePools; + std::unordered_set FilterStoragePools; + std::unordered_set FilterGroupIds; + std::unordered_set FilterNodeIds; + std::unordered_set FilterPDiskIds; + std::vector SubscriptionNodeIds; + + enum class EWith { + Everything, + MissingDisks, + SpaceProblems, + }; + + enum ETimeoutTag { + TimeoutBSC, + TimeoutFinal, + }; + + EGroupFields SortBy = EGroupFields::PoolName; + EGroupFields GroupBy = EGroupFields::GroupId; + EGroupFields FilterGroupBy = EGroupFields::GroupId; + TString FilterGroup; + EWith With = EWith::Everything; + bool ReverseSort = false; + std::optional Offset; + std::optional Limit; + ui32 SpaceUsageProblem = 90; // % + + using TGroupSortKey = std::variant; + + struct TPDisk { + ui32 PDiskId = 0; + TNodeId NodeId = 0; + TString Type; + TString Path; + ui64 Guid = 0; + ui64 AvailableSize = 0; + ui64 TotalSize = 0; + TString Status; + TInstant StatusChangeTimestamp; + ui64 EnforcedDynamicSlotSize = 0; + ui32 ExpectedSlotCount = 0; + ui32 NumActiveSlots = 0; + ui64 Category = 0; + TString DecommitStatus; + NKikimrViewer::EFlag DiskSpace = NKikimrViewer::EFlag::Grey; + + void SetCategory(ui64 category) { + Category = category; + switch (TPDiskCategory(Category).Type()) { + case NPDisk::EDeviceType::DEVICE_TYPE_ROT: + Type = "hdd"; + break; + case NPDisk::EDeviceType::DEVICE_TYPE_SSD: + Type = "ssd"; + break; + case NPDisk::EDeviceType::DEVICE_TYPE_NVME: + Type = "nvme"; + break; + case NPDisk::EDeviceType::DEVICE_TYPE_UNKNOWN: + break; + } + } + + ui64 GetSlotTotalSize() const { + if (EnforcedDynamicSlotSize) { + return EnforcedDynamicSlotSize; + } + if (ExpectedSlotCount) { + return TotalSize / ExpectedSlotCount; + } + if (NumActiveSlots) { + return TotalSize / NumActiveSlots; + } + return TotalSize; + } + + float GetDiskSpaceUsage() const { + return TotalSize ? 100.0 * (TotalSize - AvailableSize) / TotalSize : 0; + } + + TString GetPDiskId() const { + return TStringBuilder() << NodeId << '-' << PDiskId; + } + }; + + struct TVDisk { + TVDiskID VDiskId; + TVSlotId VSlotId; + ui64 AllocatedSize = 0; + ui64 AvailableSize = 0; + TString Status; + std::optional VDiskStatus; + ui64 Read = 0; + ui64 Write = 0; + NKikimrViewer::EFlag DiskSpace = NKikimrViewer::EFlag::Grey; + bool Donor = false; + std::vector Donors; + + TString GetVDiskId() const { + return TStringBuilder() << VDiskId.GroupID.GetRawId() << '-' + << VDiskId.GroupGeneration << '-' + << int(VDiskId.FailRealm) << '-' + << int(VDiskId.FailDomain) << '-' + << int(VDiskId.VDisk); + } + }; + + struct TGroup { + TString PoolName; + TGroupId GroupId = 0; + ui32 GroupGeneration = 0; + ui64 BoxId = 0; + ui64 PoolId = 0; + ui64 SchemeShardId = 0; + ui64 PathId = 0; + TString Kind; + TString MediaType; + TString Erasure; + TErasureType::EErasureSpecies ErasureSpecies = TErasureType::ErasureNone; + TString State; + ui32 EncryptionMode = 0; + ui64 AllocationUnits = 0; + float Usage = 0; + ui64 Used = 0; + ui64 Limit = 0; + ui64 Available = 0; + ui64 Read = 0; + ui64 Write = 0; + ui64 MissingDisks = 0; + ui64 PutTabletLogLatency = 0; + ui64 PutUserDataLatency = 0; + ui64 GetFastLatency = 0; + NKikimrViewer::EFlag Overall = NKikimrViewer::EFlag::Grey; + NKikimrViewer::EFlag DiskSpace = NKikimrViewer::EFlag::Grey; + float DiskSpaceUsage = 0; // the highest + + std::vector VDisks; + std::vector VDiskNodeIds; // filter nodes to request disk info from the whiteboard. could be duplicated. + + static TString PrintDomains(const std::vector& failedDomains) { + TString result; + result += ::ToString(failedDomains.size()); + result += '('; + for (ui8 domains : failedDomains) { + if (!result.empty()) { + result += ','; + } + result += ::ToString(domains); + } + result += ')'; + return result; + } + + TString GetUsageForGroup() const { + //return TStringBuilder() << std::ceil(std::clamp(Usage, 0, 100) / 5) * 5 << '%'; + // we want 0%-95% groups instead of 5%-100% groups + return TStringBuilder() << std::floor(std::clamp(Usage, 0, 100) / 5) * 5 << '%'; + } + + TString GetDiskUsageForGroup() const { + //return TStringBuilder() << std::ceil(std::clamp(DiskSpaceUsage, 0, 100) / 5) * 5 << '%'; + // we want 0%-95% groups instead of 5%-100% groups + return TStringBuilder() << std::floor(std::clamp(DiskSpaceUsage, 0, 100) / 5) * 5 << '%'; + } + + TString GetEncryptionForGroup() const { + return EncryptionMode ? "encrypted" : "not encrypted"; + } + + TString GetMissingDisksForGroup() const { + return MissingDisks == 0 ? TString("0") : TStringBuilder() << "-" << MissingDisks; + } + + static int RoundUpTime(int v) { + static const int roundUp[] = {1, 5, 10, 50, 100, 200, 500, 1000}; + auto it = std::lower_bound(std::begin(roundUp), std::end(roundUp), v); + if (it == std::end(roundUp)) { + return 1000; + } + return *it; + } + + TString GetLatencyForGroup() const { + if (PutTabletLogLatency == 0) { + return "-"; + } else if (PutTabletLogLatency < 1000) { + return TStringBuilder() << RoundUpTime(PutTabletLogLatency) << "us"; + } else if (PutTabletLogLatency < 1000000) { + return TStringBuilder() << RoundUpTime(PutTabletLogLatency / 1000) << "ms"; + } else { + return TStringBuilder() << RoundUpTime(PutTabletLogLatency / 1000000) << "s"; + } + } + + // none: ok, dead:1 + // block-4-2: ok, replicating:1 starting:1, degraded:1, degraded:2, dead:3 + // mirror-3-dc: ok, degraded:1(1), degraded:1(2), degraded:1(3), degraded:2(3,1), dead:3(3,1,1) + + void CalcState() { + MissingDisks = 0; + ui64 allocated = 0; + ui64 limit = 0; + ui32 startingDisks = 0; + ui32 replicatingDisks = 0; + static_assert(sizeof(TVDiskID::FailDomain) == 1, "expecting byte"); + static_assert(sizeof(TVDiskID::FailRealm) == 1, "expecting byte"); + std::vector failedDomainsPerRealm; + for (const TVDisk& vdisk : VDisks) { + if (vdisk.VDiskStatus && *vdisk.VDiskStatus != NKikimrBlobStorage::EVDiskStatus::READY) { + if (ErasureSpecies == TErasureType::ErasureMirror3dc) { + if (failedDomainsPerRealm.size() <= vdisk.VDiskId.FailRealm) { + failedDomainsPerRealm.resize(vdisk.VDiskId.FailRealm + 1); + } + failedDomainsPerRealm[vdisk.VDiskId.FailRealm]++; + } + ++MissingDisks; + if (*vdisk.VDiskStatus == NKikimrBlobStorage::EVDiskStatus::INIT_PENDING) { + ++startingDisks; + } + if (*vdisk.VDiskStatus == NKikimrBlobStorage::EVDiskStatus::REPLICATING) { + ++replicatingDisks; + } + } + allocated += vdisk.AllocatedSize; + limit += vdisk.AllocatedSize + vdisk.AvailableSize; + DiskSpace = std::max(DiskSpace, vdisk.DiskSpace); + } + if (MissingDisks == 0) { + Overall = NKikimrViewer::EFlag::Green; + State = "ok"; + } else { + if (ErasureSpecies == TErasureType::ErasureNone) { + TString state; + Overall = NKikimrViewer::EFlag::Red; + if (MissingDisks == startingDisks) { + state = "starting"; + } else { + state = "dead"; + } + State = TStringBuilder() << state << ':' << MissingDisks; + } else if (ErasureSpecies == TErasureType::Erasure4Plus2Block) { + TString state; + if (MissingDisks > 2) { + Overall = NKikimrViewer::EFlag::Red; + state = "dead"; + } else if (MissingDisks == 2) { + Overall = NKikimrViewer::EFlag::Orange; + state = "degraded"; + } else if (MissingDisks == 1) { + if (MissingDisks == replicatingDisks + startingDisks) { + Overall = NKikimrViewer::EFlag::Blue; + if (replicatingDisks) { + state = "replicating"; + } else { + state = "starting"; + } + } else { + Overall = NKikimrViewer::EFlag::Yellow; + state = "degraded"; + } + } + State = TStringBuilder() << state << ':' << MissingDisks; + } else if (ErasureSpecies == TErasureType::ErasureMirror3dc) { + std::sort(failedDomainsPerRealm.begin(), failedDomainsPerRealm.end(), std::greater()); + while (!failedDomainsPerRealm.empty() && failedDomainsPerRealm.back() == 0) { + failedDomainsPerRealm.pop_back(); + } + TString state; + if (failedDomainsPerRealm.size() > 2 || (failedDomainsPerRealm.size() == 2 && failedDomainsPerRealm[1] > 1)) { + Overall = NKikimrViewer::EFlag::Red; + state = "dead"; + } else if (failedDomainsPerRealm.size() == 2) { + Overall = NKikimrViewer::EFlag::Orange; + state = "degraded"; + } else if (failedDomainsPerRealm.size()) { + if (MissingDisks == replicatingDisks + startingDisks) { + Overall = NKikimrViewer::EFlag::Blue; + if (replicatingDisks > startingDisks) { + state = "replicating"; + } else { + state = "starting"; + } + } else { + Overall = NKikimrViewer::EFlag::Yellow; + state = "degraded"; + } + } + State = TStringBuilder() << state << ':' << PrintDomains(failedDomainsPerRealm); + } + } + Used = allocated; + Limit = limit; + Usage = Limit ? 100.0 * Used / Limit : 0; + if (Usage >= 95) { + DiskSpace = std::max(DiskSpace, NKikimrViewer::EFlag::Red); + } else if (Usage >= 90) { + DiskSpace = std::max(DiskSpace, NKikimrViewer::EFlag::Orange); + } else if (Usage >= 85) { + DiskSpace = std::max(DiskSpace, NKikimrViewer::EFlag::Yellow); + } else { + DiskSpace = std::max(DiskSpace, NKikimrViewer::EFlag::Green); + } + } + + void CalcAvailableAndDiskSpace(const std::unordered_map& pDisks) { + ui64 available = 0; + DiskSpace = NKikimrViewer::EFlag::Grey; + DiskSpaceUsage = 0; + for (const TVDisk& vdisk : VDisks) { + auto itPDisk = pDisks.find(vdisk.VSlotId); + if (itPDisk != pDisks.end()) { + available += std::min(itPDisk->second.GetSlotTotalSize() - vdisk.AllocatedSize, vdisk.AvailableSize); + DiskSpace = std::max(DiskSpace, vdisk.DiskSpace); + DiskSpaceUsage = std::max(DiskSpaceUsage, itPDisk->second.GetDiskSpaceUsage()); + } + } + Available = available; + } + + void CalcReadWrite() { + ui64 read = 0; + ui64 write = 0; + for (const TVDisk& vdisk : VDisks) { + read += vdisk.Read; + write += vdisk.Write; + } + Read = read; + Write = write; + } + + ui64 GetLatencyForSort() const { + return PutTabletLogLatency; + } + + TString GetGroupName(EGroupFields groupBy) const { + TString groupName; + switch (groupBy) { + case EGroupFields::GroupId: + groupName = ToString(GroupId); + break; + case EGroupFields::Erasure: + groupName = Erasure; + break; + case EGroupFields::Usage: + groupName = GetUsageForGroup(); + break; + case EGroupFields::DiskSpaceUsage: + groupName = GetDiskUsageForGroup(); + break; + case EGroupFields::PoolName: + groupName = PoolName; + break; + case EGroupFields::Kind: + groupName = Kind; + break; + case EGroupFields::Encryption: + groupName = GetEncryptionForGroup(); + break; + case EGroupFields::MediaType: + groupName = MediaType; + break; + case EGroupFields::MissingDisks: + groupName = GetMissingDisksForGroup(); + break; + case EGroupFields::State: + groupName = State; + break; + case EGroupFields::Latency: + groupName = GetLatencyForGroup(); + break; + default: + break; + } + if (groupName.empty()) { + groupName = "unknown"; + } + return groupName; + } + + TGroupSortKey GetSortKey(EGroupFields groupBy) const { + switch (groupBy) { + case EGroupFields::GroupId: + case EGroupFields::Erasure: + case EGroupFields::PoolName: + case EGroupFields::Kind: + case EGroupFields::MediaType: + case EGroupFields::State: + return GetGroupName(groupBy); + case EGroupFields::Usage: + return Usage; + case EGroupFields::DiskSpaceUsage: + return DiskSpaceUsage; + case EGroupFields::Encryption: + return EncryptionMode; + case EGroupFields::MissingDisks: + return MissingDisks; + case EGroupFields::Latency: + return PutTabletLogLatency; + default: + return TString(); + } + } + }; + + using TGroupData = std::vector; + using TGroupView = std::vector; + + struct TGroupGroup { + TString Name; + TGroupSortKey SortKey; + TGroupView Groups; + + // stats + float Usage = 0; // avg + ui64 Used = 0; // sum + ui64 Limit = 0; // sum + ui64 Available = 0; // sum + ui64 Read = 0; // sum + ui64 Write = 0; // sum + ui64 PutTabletLogLatency = 0; // max + ui64 PutUserDataLatency = 0; // max + ui64 GetFastLatency = 0; // max + float DiskSpaceUsage = 0; // max + + void CalcStats() { + for (TGroup* group : Groups) { + Usage += group->Usage; + Used += group->Used; + Limit += group->Limit; + Read += group->Read; + Write += group->Write; + if (Available) { + Available = std::min(Available, group->Available); + } else { + Available = group->Available; + } + PutTabletLogLatency = std::max(PutTabletLogLatency, group->PutTabletLogLatency); + PutUserDataLatency = std::max(PutUserDataLatency, group->PutUserDataLatency); + GetFastLatency = std::max(GetFastLatency, group->GetFastLatency); + DiskSpaceUsage = std::max(DiskSpaceUsage, group->DiskSpaceUsage); + } + if (!Groups.empty()) { + Usage /= Groups.size(); + } + } + }; + + TGroupData GroupData; + TGroupView GroupView; + std::vector GroupGroups; + std::unordered_map GroupsByGroupId; + std::unordered_map PDisks; + std::unordered_map VSlotsByVSlotId; + std::unordered_map VDisksByVSlotId; + std::unordered_map PDisksByPDiskId; + + TFieldsType FieldsRequired; + TFieldsType FieldsAvailable; + const TFieldsType FieldsAll = TFieldsType().set(); + const TFieldsType FieldsBsGroups = TFieldsType().set(+EGroupFields::GroupId) + .set(+EGroupFields::Erasure) + .set(+EGroupFields::Latency); + const TFieldsType FieldsBsPools = TFieldsType().set(+EGroupFields::PoolName) + .set(+EGroupFields::Kind) + .set(+EGroupFields::MediaType) + .set(+EGroupFields::Encryption); + const TFieldsType FieldsBsVSlots = TFieldsType().set(+EGroupFields::NodeId) + .set(+EGroupFields::PDiskId) + .set(+EGroupFields::VDisk); + const TFieldsType FieldsBsPDisks = TFieldsType().set(+EGroupFields::PDisk); + const TFieldsType FieldsGroupState = TFieldsType().set(+EGroupFields::Used) + .set(+EGroupFields::Limit) + .set(+EGroupFields::Usage) + .set(+EGroupFields::MissingDisks) + .set(+EGroupFields::State); + const TFieldsType FieldsGroupAvailableAndDiskSpace = TFieldsType().set(+EGroupFields::Available) + .set(+EGroupFields::DiskSpaceUsage); + const TFieldsType FieldsHive = TFieldsType().set(+EGroupFields::AllocationUnits); + const TFieldsType FieldsWbGroups = TFieldsType().set(+EGroupFields::GroupId) + .set(+EGroupFields::Erasure) + .set(+EGroupFields::PoolName) + .set(+EGroupFields::Encryption); + const TFieldsType FieldsWbDisks = TFieldsType().set(+EGroupFields::NodeId) + .set(+EGroupFields::PDiskId) + .set(+EGroupFields::VDisk) + .set(+EGroupFields::PDisk) + .set(+EGroupFields::Read) + .set(+EGroupFields::Write); + + const std::unordered_map DependentFields = { + { EGroupFields::DiskSpaceUsage, TFieldsType().set(+EGroupFields::PDisk) + .set(+EGroupFields::VDisk) }, + { EGroupFields::Available, TFieldsType().set(+EGroupFields::PDisk) + .set(+EGroupFields::VDisk) }, + { EGroupFields::Read, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::Write, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::Used, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::Limit, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::Usage, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::MissingDisks, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::State, TFieldsType().set(+EGroupFields::VDisk) }, + { EGroupFields::Encryption, TFieldsType().set(+EGroupFields::VDisk) }, + }; + + bool FieldsNeeded(TFieldsType fields) const { + return (FieldsRequired & (fields & ~FieldsAvailable)).any(); + } + + bool NeedFilter = false; + bool NeedGroup = false; + bool NeedSort = false; + bool NeedLimit = false; + ui64 TotalGroups = 0; + ui64 FoundGroups = 0; + std::vector Problems; + + void AddProblem(const TString& problem) { + for (const auto& p : Problems) { + if (p == problem) { + return; + } + } + Problems.push_back(problem); + } + + static EGroupFields ParseEGroupFields(TStringBuf field) { + EGroupFields result = EGroupFields::COUNT; + if (field == "PoolName") { + result = EGroupFields::PoolName; + } else if (field == "Kind") { + result = EGroupFields::Kind; + } else if (field == "MediaType") { + result = EGroupFields::MediaType; + } else if (field == "Erasure") { + result = EGroupFields::Erasure; + } else if (field == "Degraded" || field == "MissingDisks") { + result = EGroupFields::MissingDisks; + } else if (field == "State") { + result = EGroupFields::State; + } else if (field == "Usage") { + result = EGroupFields::Usage; + } else if (field == "GroupId") { + result = EGroupFields::GroupId; + } else if (field == "Encryption") { + result = EGroupFields::Encryption; + } else if (field == "Used") { + result = EGroupFields::Used; + } else if (field == "Limit") { + result = EGroupFields::Limit; + } else if (field == "Read") { + result = EGroupFields::Read; + } else if (field == "Write") { + result = EGroupFields::Write; + } else if (field == "AllocationUnits") { + result = EGroupFields::AllocationUnits; + } else if (field == "VDisk") { + result = EGroupFields::VDisk; + } else if (field == "PDisk") { + result = EGroupFields::PDisk; + } else if (field == "DiskSpaceUsage") { + result = EGroupFields::DiskSpaceUsage; + } else if (field == "NodeId") { + result = EGroupFields::NodeId; + } else if (field == "PDiskId") { + result = EGroupFields::PDiskId; + } else if (field == "Latency") { + result = EGroupFields::Latency; + } else if (field == "Available") { + result = EGroupFields::Available; + } + return result; + } + + TStorageGroups(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev, "/storage/groups") + { + const auto& params(Event->Get()->Request.GetParams()); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + if (!Database.empty()) { + FieldsRequired.set(+EGroupFields::PoolName); + NeedFilter = true; + } + FieldsRequired.set(+EGroupFields::GroupId); + TString filterStoragePool = params.Get("pool"); + if (!filterStoragePool.empty()) { + FilterStoragePools.emplace(filterStoragePool); + } + SplitIds(params.Get("node_id"), ',', FilterNodeIds); + SplitIds(params.Get("pdisk_id"), ',', FilterPDiskIds); + SplitIds(params.Get("group_id"), ',', FilterGroupIds); + if (!FilterStoragePools.empty()) { + FieldsRequired.set(+EGroupFields::PoolName); + NeedFilter = true; + } + if (!FilterNodeIds.empty()) { + FieldsRequired.set(+EGroupFields::NodeId); + NeedFilter = true; + } + if (!FilterPDiskIds.empty()) { + FieldsRequired.set(+EGroupFields::PDiskId); + NeedFilter = true; + } + if (!FilterGroupIds.empty()) { + FieldsRequired.set(+EGroupFields::PoolName); + NeedFilter = true; + } + if (params.Has("filter")) { + Filter = params.Get("filter"); + FieldsRequired.set(+EGroupFields::PoolName); + FieldsRequired.set(+EGroupFields::GroupId); + NeedFilter = true; + } + if (params.Has("filter_group") && params.Has("filter_group_by")) { + FilterGroup = params.Get("filter_group"); + FilterGroupBy = ParseEGroupFields(params.Get("filter_group_by")); + FieldsRequired.set(+FilterGroupBy); + NeedFilter = true; + } + if (params.Get("with") == "missing") { + With = EWith::MissingDisks; + FieldsRequired.set(+EGroupFields::MissingDisks); + NeedFilter = true; + } if (params.Get("with") == "space") { + With = EWith::SpaceProblems; + FieldsRequired.set(+EGroupFields::Available); + NeedFilter = true; + } + if (params.Has("offset")) { + Offset = FromStringWithDefault(params.Get("offset"), 0); + NeedLimit = true; + } + if (params.Has("limit")) { + Limit = FromStringWithDefault(params.Get("limit"), std::numeric_limits::max()); + NeedLimit = true; + } + TStringBuf sort = params.Get("sort"); + if (sort) { + NeedSort = true; + if (sort.StartsWith("-") || sort.StartsWith("+")) { + ReverseSort = (sort[0] == '-'); + sort.Skip(1); + } + SortBy = ParseEGroupFields(sort); + FieldsRequired.set(+SortBy); + } + bool whiteboardOnly = FromStringWithDefault(params.Get("whiteboard_only"), false); + if (whiteboardOnly) { + FieldsRequired |= FieldsWbGroups; + FieldsRequired |= FieldsWbDisks; + FallbackToWhiteboard = true; + } + bool bscOnly = FromStringWithDefault(params.Get("bsc_only"), false); + if (bscOnly) { + FieldsRequired |= FieldsBsGroups; + FieldsRequired |= FieldsBsPools; + FieldsRequired |= FieldsBsVSlots; + FieldsRequired |= FieldsBsPDisks; + } + TString fieldsRequired = params.Get("fields_required"); + if (!fieldsRequired.empty()) { + if (fieldsRequired == "all") { + FieldsRequired = FieldsAll; + } else { + TStringBuf source = fieldsRequired; + for (TStringBuf value = source.NextTok(','); !value.empty(); value = source.NextTok(',')) { + EGroupFields field = ParseEGroupFields(value); + if (field != EGroupFields::COUNT) { + FieldsRequired.set(+field); + } + } + } + } + TStringBuf group = params.Get("group"); + if (group) { + NeedGroup = true; + GroupBy = ParseEGroupFields(group); + FieldsRequired.set(+GroupBy); + NeedSort = false; + NeedLimit = false; + } + for (auto field = +EGroupFields::GroupId; field != +EGroupFields::COUNT; ++field) { + if (FieldsRequired.test(field)) { + auto itDependentFields = DependentFields.find(static_cast(field)); + if (itDependentFields != DependentFields.end()) { + FieldsRequired |= itDependentFields->second; + } + } + } + } + +public: + void Bootstrap() override { + if (TBase::NeedToRedirect()) { + return; + } + if (Database) { + if (!DatabaseNavigateResponse) { + DatabaseNavigateResponse = MakeRequestSchemeCacheNavigate(Database, 0); + ++NavigateKeySetInFlight; + } else { + auto pathId = GetPathId(DatabaseNavigateResponse->GetRef()); + auto result = NavigateKeySetResult.emplace(pathId, std::move(*DatabaseNavigateResponse)); + ProcessNavigate(result.first->second, true); + } + } + if (FallbackToWhiteboard) { + RequestWhiteboard(); + } else { + if (FieldsNeeded(FieldsBsGroups)) { + GetGroupsResponse = RequestBSControllerGroups(); + } + if (FieldsNeeded(FieldsBsPools)) { + GetStoragePoolsResponse = RequestBSControllerPools(); + } + if (FieldsNeeded(FieldsBsVSlots)) { + GetVSlotsResponse = RequestBSControllerVSlots(); + } + if (FieldsNeeded(FieldsBsPDisks)) { + GetPDisksResponse = RequestBSControllerPDisks(); + } + } + + if (Requests == 0) { + return ReplyAndPassAway(); + } + TBase::Become(&TThis::StateWork); + Schedule(TDuration::MilliSeconds(Timeout * 50 / 100), new TEvents::TEvWakeup(TimeoutBSC)); // 50% timeout (for bsc) + Schedule(TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup(TimeoutFinal)); // timeout for the rest + } + + void PassAway() override { + std::vector passedNodes; + for (const TNodeId nodeId : SubscriptionNodeIds) { + if (passedNodes.size() <= nodeId) { + passedNodes.resize(nodeId + 1); + } else { + if (passedNodes[nodeId]) { + continue; + } + } + Send(TActivationContext::InterconnectProxy(nodeId), new TEvents::TEvUnsubscribe()); + passedNodes[nodeId] = true; + } + TBase::PassAway(); + } + + void ApplyFilter() { + // database pre-filter, affects TotalGroups count + if (!DatabaseStoragePools.empty()) { + if (FieldsAvailable.test(+EGroupFields::PoolName)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (DatabaseStoragePools.count(group->PoolName)) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + DatabaseStoragePools.clear(); + FoundGroups = TotalGroups = GroupView.size(); + GroupsByGroupId.clear(); + } else { + return; + } + } + // group id pre-filter, affects TotalGroups count + if (!FilterGroupIds.empty()) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (FilterGroupIds.count(group->GroupId)) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + FoundGroups = TotalGroups = GroupView.size(); + FilterGroupIds.clear(); + GroupsByGroupId.clear(); + } + // storage pool pre-filter, affects TotalGroups count + if (!FilterStoragePools.empty()) { + if (FieldsAvailable.test(+EGroupFields::PoolName)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (FilterStoragePools.count(group->PoolName)) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + FoundGroups = TotalGroups = GroupView.size(); + FilterStoragePools.clear(); + GroupsByGroupId.clear(); + } else { + return; + } + } + // node_id + pdisk_id pre-filter, affects TotalGroups count + if (!FilterNodeIds.empty() && !FilterPDiskIds.empty()) { + if (FieldsAvailable.test(+EGroupFields::NodeId) && FieldsAvailable.test(+EGroupFields::PDiskId)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + for (const auto& vdisk : group->VDisks) { + if (FilterNodeIds.count(vdisk.VSlotId.NodeId) && FilterPDiskIds.count(vdisk.VSlotId.PDiskId)) { + groupView.push_back(group); + break; + } + } + } + GroupView.swap(groupView); + FoundGroups = TotalGroups = GroupView.size(); + FilterNodeIds.clear(); + FilterPDiskIds.clear(); + GroupsByGroupId.clear(); + } else { + return; + } + } + // node_id pre-filter, affects TotalGroups count + if (!FilterNodeIds.empty()) { + if (FieldsAvailable.test(+EGroupFields::NodeId)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + for (const auto& vdisk : group->VDisks) { + if (FilterNodeIds.count(vdisk.VSlotId.NodeId)) { + groupView.push_back(group); + break; + } + } + } + GroupView.swap(groupView); + FoundGroups = TotalGroups = GroupView.size(); + FilterNodeIds.clear(); + GroupsByGroupId.clear(); + } else { + return; + } + } + // pdisk_id pre-filter, affects TotalGroups count + if (!FilterPDiskIds.empty()) { + if (FieldsAvailable.test(+EGroupFields::PDiskId)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + for (const auto& vdisk : group->VDisks) { + if (FilterPDiskIds.count(vdisk.VSlotId.PDiskId)) { + groupView.push_back(group); + break; + } + } + } + GroupView.swap(groupView); + FoundGroups = TotalGroups = GroupView.size(); + FilterPDiskIds.clear(); + GroupsByGroupId.clear(); + } else { + return; + } + } + if (NeedFilter) { + if (With == EWith::MissingDisks && FieldsAvailable.test(+EGroupFields::MissingDisks)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (group->MissingDisks != 0) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + With = EWith::Everything; + GroupsByGroupId.clear(); + } + if (With == EWith::SpaceProblems && FieldsAvailable.test(+EGroupFields::Usage)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (group->Usage >= SpaceUsageProblem) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + With = EWith::Everything; + GroupsByGroupId.clear(); + } + if (!Filter.empty() && FieldsAvailable.test(+EGroupFields::PoolName) && FieldsAvailable.test(+EGroupFields::GroupId)) { + TVector filterWords = SplitString(Filter, " "); + TGroupView groupView; + for (TGroup* group : GroupView) { + bool match = false; + for (const TString& word : filterWords) { + if (group->PoolName.Contains(word)) { + match = true; + break; + } else if (::ToString(group->GroupId).Contains(word)) { + match = true; + break; + } + } + if (match) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + Filter.clear(); + GroupsByGroupId.clear(); + } + if (!FilterGroup.empty() && FieldsAvailable.test(+FilterGroupBy)) { + TGroupView groupView; + for (TGroup* group : GroupView) { + if (group->GetGroupName(FilterGroupBy) == FilterGroup) { + groupView.push_back(group); + } + } + GroupView.swap(groupView); + FilterGroup.clear(); + GroupsByGroupId.clear(); + } + NeedFilter = (With != EWith::Everything) || !Filter.empty() || !FilterStoragePools.empty() || !FilterNodeIds.empty() || !FilterPDiskIds.empty() || !FilterGroupIds.empty() || !FilterGroup.empty(); + FoundGroups = GroupView.size(); + } + } + + void GroupCollection() { + std::unordered_map groupGroups; + GroupGroups.clear(); + for (TGroup* group : GroupView) { + auto gb = group->GetGroupName(GroupBy); + TGroupGroup* groupGroup = nullptr; + auto it = groupGroups.find(gb); + if (it == groupGroups.end()) { + groupGroups.emplace(gb, GroupGroups.size()); + groupGroup = &GroupGroups.emplace_back(); + groupGroup->Name = gb; + groupGroup->SortKey = group->GetSortKey(GroupBy); + } else { + groupGroup = &GroupGroups[it->second]; + } + groupGroup->Groups.push_back(group); + } + } + + void ApplyGroup() { + if (!NeedFilter && NeedGroup && FieldsAvailable.test(+GroupBy)) { + switch (GroupBy) { + case EGroupFields::GroupId: + case EGroupFields::Erasure: + case EGroupFields::PoolName: + case EGroupFields::Kind: + case EGroupFields::Encryption: + case EGroupFields::MediaType: + case EGroupFields::State: + GroupCollection(); + SortCollection(GroupGroups, [](const TGroupGroup& groupGroup) { return groupGroup.SortKey; }); + NeedGroup = false; + break; + case EGroupFields::Usage: + case EGroupFields::DiskSpaceUsage: + case EGroupFields::MissingDisks: + case EGroupFields::Latency: + GroupCollection(); + SortCollection(GroupGroups, [](const TGroupGroup& groupGroup) { return groupGroup.SortKey; }, true); + NeedGroup = false; + break; + case EGroupFields::Read: + case EGroupFields::Write: + case EGroupFields::NodeId: + case EGroupFields::PDisk: + case EGroupFields::VDisk: + case EGroupFields::COUNT: + case EGroupFields::Used: + case EGroupFields::Limit: + case EGroupFields::Available: + case EGroupFields::AllocationUnits: + case EGroupFields::PDiskId: + break; + } + } + } + + void ApplySort() { + if (NeedSort && FieldsAvailable.test(+SortBy)) { + switch (SortBy) { + case EGroupFields::GroupId: + SortCollection(GroupView, [](const TGroup* group) { return group->GroupId; }, ReverseSort); + break; + case EGroupFields::Erasure: + SortCollection(GroupView, [](const TGroup* group) { return group->Erasure; }, ReverseSort); + break; + case EGroupFields::Usage: + SortCollection(GroupView, [](const TGroup* group) { return group->Usage; }, ReverseSort); + break; + case EGroupFields::Used: + SortCollection(GroupView, [](const TGroup* group) { return group->Used; }, ReverseSort); + break; + case EGroupFields::Limit: + SortCollection(GroupView, [](const TGroup* group) { return group->Limit; }, ReverseSort); + break; + case EGroupFields::Available: + SortCollection(GroupView, [](const TGroup* group) { return group->Available; }, ReverseSort); + break; + case EGroupFields::DiskSpaceUsage: + SortCollection(GroupView, [](const TGroup* group) { return group->DiskSpaceUsage; }, ReverseSort); + break; + case EGroupFields::PoolName: + SortCollection(GroupView, [](const TGroup* group) { return group->PoolName; }, ReverseSort); + break; + case EGroupFields::Kind: + SortCollection(GroupView, [](const TGroup* group) { return group->Kind; }, ReverseSort); + break; + case EGroupFields::Encryption: + SortCollection(GroupView, [](const TGroup* group) { return group->EncryptionMode; }, ReverseSort); + break; + case EGroupFields::AllocationUnits: + SortCollection(GroupView, [](const TGroup* group) { return group->AllocationUnits; }, ReverseSort); + break; + case EGroupFields::MediaType: + SortCollection(GroupView, [](const TGroup* group) { return group->MediaType; }, ReverseSort); + break; + case EGroupFields::MissingDisks: + SortCollection(GroupView, [](const TGroup* group) { return group->MissingDisks; }, ReverseSort); + break; + case EGroupFields::Read: + SortCollection(GroupView, [](const TGroup* group) { return group->Read; }, ReverseSort); + break; + case EGroupFields::Write: + SortCollection(GroupView, [](const TGroup* group) { return group->Write; }, ReverseSort); + break; + case EGroupFields::State: + SortCollection(GroupView, [](const TGroup* group) { return group->State; }, ReverseSort); + break; + case EGroupFields::Latency: + SortCollection(GroupView, [](const TGroup* group) { return group->GetLatencyForSort(); }, ReverseSort); + break; + case EGroupFields::PDiskId: + case EGroupFields::NodeId: + case EGroupFields::PDisk: + case EGroupFields::VDisk: + case EGroupFields::COUNT: + break; + } + NeedSort = false; + GroupsByGroupId.clear(); + } + } + + void ApplyLimit() { + if (!NeedFilter && !NeedSort && !NeedGroup && NeedLimit) { + if (Offset) { + GroupView.erase(GroupView.begin(), GroupView.begin() + std::min(*Offset, GroupView.size())); + GroupsByGroupId.clear(); + } + if (Limit) { + GroupView.resize(std::min(*Limit, GroupView.size())); + GroupsByGroupId.clear(); + } + NeedLimit = false; + } + } + + void ApplyEverything() { + ApplyFilter(); + ApplyGroup(); + ApplySort(); + ApplyLimit(); + } + + void CollectHiveData() { + if (FieldsNeeded(FieldsHive)) { + if (!GroupView.empty()) { + ui64 hiveId = AppData()->DomainsInfo->GetHive(); + if (hiveId != TDomainsInfo::BadTabletId) { + if (HiveStorageStats.count(hiveId) == 0) { + HiveStorageStats.emplace(hiveId, MakeRequestHiveStorageStats(hiveId)); + ++HiveStorageStatsInFlight; + } + } + } + for (const TGroup* group : GroupView) { + TPathId pathId(group->SchemeShardId, group->PathId); + if (NavigateKeySetResult.count(pathId) == 0) { + ui64 cookie = NavigateKeySetResult.size(); + NavigateKeySetResult.emplace(pathId, MakeRequestSchemeCacheNavigate(pathId, cookie)); + ++NavigateKeySetInFlight; + } + } + } + } + + void RebuildGroupsByGroupId() { + GroupsByGroupId.clear(); + for (TGroup* group : GroupView) { + GroupsByGroupId.emplace(group->GroupId, group); + } + } + + static TString GetMediaType(const TString& mediaType) { + if (mediaType.StartsWith("Type:")) { + return mediaType.substr(5); + } + return mediaType; + } + + void FillVDiskFromVSlotInfo(TVDisk& vDisk, TVSlotId vSlotId, const NKikimrSysView::TVSlotInfo& info) { + vDisk.VDiskId = TVDiskID(info.GetGroupId(), + info.GetGroupGeneration(), + static_cast(info.GetFailRealm()), + static_cast(info.GetFailDomain()), + static_cast(info.GetVDisk())); + vDisk.VSlotId = vSlotId; + vDisk.AllocatedSize = info.GetAllocatedSize(); + vDisk.AvailableSize = info.GetAvailableSize(); + //vDisk.Kind = info.GetKind(); + vDisk.Status = info.GetStatusV2(); + NKikimrBlobStorage::EVDiskStatus vDiskStatus; + if (vDisk.Status && NKikimrBlobStorage::EVDiskStatus_Parse(vDisk.Status, &vDiskStatus)) { + vDisk.VDiskStatus = vDiskStatus; + } + } + + bool AreBSControllerRequestsDone() const { + return !GetGroupsResponse && !GetStoragePoolsResponse && !GetVSlotsResponse && !GetPDisksResponse; + } + + bool TimeToAskWhiteboard() const { + return AreBSControllerRequestsDone() && + NavigateKeySetInFlight == 0 && + HiveStorageStatsInFlight == 0; + } + + void ProcessResponses() { + AddEvent("ProcessResponses"); + if (GetGroupsResponse && GetGroupsResponse->IsDone()) { + if (GetGroupsResponse->IsOk()) { + GroupData.reserve(GetGroupsResponse->Get()->Record.EntriesSize()); + for (const NKikimrSysView::TGroupEntry& entry : GetGroupsResponse->Get()->Record.GetEntries()) { + const NKikimrSysView::TGroupInfo& info = entry.GetInfo(); + TGroup& group = GroupData.emplace_back(); + group.GroupId = entry.GetKey().GetGroupId(); + group.GroupGeneration = info.GetGeneration(); + group.BoxId = info.GetBoxId(); + group.PoolId = info.GetStoragePoolId(); + group.Erasure = info.GetErasureSpeciesV2(); + group.ErasureSpecies = TErasureType::ErasureSpeciesByName(group.Erasure); + //group.Used = info.GetAllocatedSize(); + //group.Limit = info.GetAllocatedSize() + info.GetAvailableSize(); + //group.Usage = group.Limit ? 100.0 * group.Used / group.Limit : 0; + group.PutTabletLogLatency = info.GetPutTabletLogLatency(); + group.PutUserDataLatency = info.GetPutUserDataLatency(); + group.GetFastLatency = info.GetGetFastLatency(); + } + for (TGroup& group : GroupData) { + GroupView.emplace_back(&group); + } + GroupsByGroupId.clear(); + FoundGroups = TotalGroups = GroupView.size(); + FieldsAvailable |= FieldsBsGroups; + ApplyEverything(); + } else { + AddProblem("bsc-storage-groups-no-data"); + } + GetGroupsResponse.reset(); + } + if (FieldsAvailable.test(+EGroupFields::GroupId) && GetStoragePoolsResponse && GetStoragePoolsResponse->IsDone()) { + if (GetStoragePoolsResponse->IsOk()) { + std::unordered_map, const NKikimrSysView::TStoragePoolInfo*> indexStoragePool; // (box, id) -> pool + for (const NKikimrSysView::TStoragePoolEntry& entry : GetStoragePoolsResponse->Get()->Record.GetEntries()) { + const auto& key = entry.GetKey(); + const NKikimrSysView::TStoragePoolInfo& pool = entry.GetInfo(); + indexStoragePool.emplace(std::make_pair(key.GetBoxId(), key.GetStoragePoolId()), &pool); + } + ui64 rootSchemeshardId = AppData()->DomainsInfo->Domain->SchemeRoot; + for (TGroup* group : GroupView) { + if (group->BoxId == 0 && group->PoolId == 0) { + group->PoolName = "static"; + group->Kind = ""; // TODO ? + group->MediaType = ""; // TODO ? + group->SchemeShardId = rootSchemeshardId; + group->PathId = 1; + group->EncryptionMode = 0; // TODO ? + } else { + auto itStoragePool = indexStoragePool.find({group->BoxId, group->PoolId}); + if (itStoragePool != indexStoragePool.end()) { + const NKikimrSysView::TStoragePoolInfo* pool = itStoragePool->second; + group->PoolName = pool->GetName(); + group->Kind = pool->GetKind(); + group->SchemeShardId = pool->GetSchemeshardId(); + group->PathId = pool->GetPathId(); + group->MediaType = GetMediaType(pool->GetPDiskFilter()); + if (!group->Erasure) { + group->Erasure = pool->GetErasureSpeciesV2(); + group->ErasureSpecies = TErasureType::ErasureSpeciesByName(group->Erasure); + } + group->EncryptionMode = pool->GetEncryptionMode(); + } else { + BLOG_W("Storage pool not found for group " << group->GroupId << " box " << group->BoxId << " pool " << group->PoolId); + } + } + } + FieldsAvailable |= FieldsBsPools; + ApplyEverything(); + CollectHiveData(); + } else { + AddProblem("bsc-storage-pools-no-data"); + } + GetStoragePoolsResponse.reset(); + } + if (FieldsAvailable.test(+EGroupFields::GroupId) && GetVSlotsResponse && GetVSlotsResponse->IsDone()) { + if (GetVSlotsResponse->IsOk()) { + if (GroupsByGroupId.empty()) { + RebuildGroupsByGroupId(); + } + size_t totalEntries = GetVSlotsResponse->Get()->Record.EntriesSize(); + size_t errorEntries = 0; + for (const NKikimrSysView::TVSlotEntry& entry : GetVSlotsResponse->Get()->Record.GetEntries()) { + const NKikimrSysView::TVSlotKey& key = entry.GetKey(); + const NKikimrSysView::TVSlotInfo& info = entry.GetInfo(); + VSlotsByVSlotId[key] = &info; + if (info.GetStatusV2() == "ERROR") { + ++errorEntries; + } + auto itGroup = GroupsByGroupId.find(info.GetGroupId()); + if (itGroup != GroupsByGroupId.end() && itGroup->second->GroupGeneration == info.GetGroupGeneration()) { + TGroup& group = *itGroup->second; + TVDisk& vDisk = group.VDisks.emplace_back(); + FillVDiskFromVSlotInfo(vDisk, key, info); + group.VDiskNodeIds.push_back(vDisk.VSlotId.NodeId); + } + } + if (totalEntries > 0 && totalEntries > errorEntries) { + FieldsAvailable |= FieldsBsVSlots; + ApplyEverything(); + } else { + AddProblem("bsc-wrong-data"); + } + } else { + AddProblem("bsc-vslots-no-data"); + } + GetVSlotsResponse.reset(); + } + if (GetPDisksResponse && GetPDisksResponse->IsDone()) { + if (GetPDisksResponse->IsOk()) { + for (const NKikimrSysView::TPDiskEntry& entry : GetPDisksResponse->Get()->Record.GetEntries()) { + const NKikimrSysView::TPDiskKey& key = entry.GetKey(); + const NKikimrSysView::TPDiskInfo& info = entry.GetInfo(); + TPDisk& pDisk = PDisks[key]; + pDisk.PDiskId = key.GetPDiskId(); + pDisk.NodeId = key.GetNodeId(); + pDisk.SetCategory(info.GetCategory()); + pDisk.Path = info.GetPath(); + pDisk.Guid = info.GetGuid(); + pDisk.AvailableSize = info.GetAvailableSize(); + pDisk.TotalSize = info.GetTotalSize(); + pDisk.Status = info.GetStatusV2(); + pDisk.StatusChangeTimestamp = TInstant::MicroSeconds(info.GetStatusChangeTimestamp()); + pDisk.EnforcedDynamicSlotSize = info.GetEnforcedDynamicSlotSize(); + pDisk.ExpectedSlotCount = info.GetExpectedSlotCount(); + pDisk.NumActiveSlots = info.GetNumActiveSlots(); + pDisk.Category = info.GetCategory(); + pDisk.DecommitStatus = info.GetDecommitStatus(); + } + FieldsAvailable |= FieldsBsPDisks; + ApplyEverything(); + } else { + AddProblem("bsc-pdisks-no-data"); + } + GetPDisksResponse.reset(); + } + if (FieldsAvailable.test(+EGroupFields::VDisk)) { + if (FieldsNeeded(FieldsGroupState)) { + for (TGroup* group : GroupView) { + group->CalcState(); + } + FieldsAvailable |= FieldsGroupState; + ApplyEverything(); + } + if (FieldsAvailable.test(+EGroupFields::PDisk)) { + if (FieldsNeeded(FieldsGroupAvailableAndDiskSpace)) { + for (TGroup* group : GroupView) { + group->CalcAvailableAndDiskSpace(PDisks); + } + FieldsAvailable |= FieldsGroupAvailableAndDiskSpace; + ApplyEverything(); + } + } + } + if (AreBSControllerRequestsDone()) { + if (FieldsAvailable.test(+EGroupFields::GroupId) && FieldsNeeded(FieldsHive) && NavigateKeySetInFlight == 0 && HiveStorageStatsInFlight == 0) { + if (GroupsByGroupId.empty()) { + RebuildGroupsByGroupId(); + } + for (auto& hiveStorageStats : HiveStorageStats) { + if (hiveStorageStats.second.IsOk()) { + for (const auto& pbPool : hiveStorageStats.second->Record.GetPools()) { + for (const auto& pbGroup : pbPool.GetGroups()) { + auto itGroup = GroupsByGroupId.find(pbGroup.GetGroupID()); + if (itGroup != GroupsByGroupId.end()) { + itGroup->second->AllocationUnits += pbGroup.GetAcquiredUnits(); + } + } + } + } + } + FieldsAvailable |= FieldsHive; + ApplyEverything(); + } + if (TimeToAskWhiteboard() && FieldsNeeded(FieldsWbDisks)) { + AddEvent("TimeToAskWhiteboard"); + for (TGroup* group : GroupView) { + for (TNodeId nodeId : group->VDiskNodeIds) { + SendWhiteboardDisksRequest(nodeId); + } + } + } + } + } + + void ProcessNavigate(TRequestResponse& navigateResult, bool firstNavigate) { + if (navigateResult.IsOk()) { + TString path = CanonizePath(navigateResult->Request->ResultSet.begin()->Path); + TIntrusiveConstPtr domainDescription = navigateResult->Request->ResultSet.begin()->DomainDescription; + TIntrusiveConstPtr domainInfo = navigateResult->Request->ResultSet.begin()->DomainInfo; + if (domainInfo != nullptr && domainDescription != nullptr) { + if (FieldsNeeded(FieldsHive)) { + TTabletId hiveId = domainInfo->Params.GetHive(); + if (hiveId != 0 && HiveStorageStats.count(hiveId) == 0) { + HiveStorageStats.emplace(hiveId, MakeRequestHiveStorageStats(hiveId)); + ++HiveStorageStatsInFlight; + } + } + if (Database && firstNavigate) { // filter by database + for (const auto& storagePool : domainDescription->Description.GetStoragePools()) { + TString storagePoolName = storagePool.GetName(); + DatabaseStoragePools.emplace(storagePoolName); + } + NeedFilter = true; + } + } + } + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + bool firstNavigate = (ev->Cookie == 0); + TPathId pathId = GetPathId(ev); + if (firstNavigate && DatabaseNavigateResponse && pathId) { + NavigateKeySetResult.emplace(pathId, std::move(*DatabaseNavigateResponse)); + } + auto itNavigateKeySetResult = NavigateKeySetResult.find(pathId); + if (itNavigateKeySetResult == NavigateKeySetResult.end()) { + BLOG_W("Invalid NavigateKeySetResult PathId: " << pathId << " Path: " << CanonizePath(ev->Get()->Request->ResultSet.begin()->Path)); + return RequestDone(); + } + auto& navigateResult(itNavigateKeySetResult->second); + navigateResult.Set(std::move(ev)); + ProcessNavigate(navigateResult, firstNavigate); + --NavigateKeySetInFlight; + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvHive::TEvResponseHiveStorageStats::TPtr& ev) { + auto itHiveStorageStats = HiveStorageStats.find(ev->Cookie); + if (itHiveStorageStats != HiveStorageStats.end()) { + itHiveStorageStats->second.Set(std::move(ev)); + } + --HiveStorageStatsInFlight; + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetGroupsResponse::TPtr& ev) { + GetGroupsResponse->Set(std::move(ev)); + if (FallbackToWhiteboard) { + RequestDone(); + return; + } + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetStoragePoolsResponse::TPtr& ev) { + GetStoragePoolsResponse->Set(std::move(ev)); + if (FallbackToWhiteboard) { + RequestDone(); + return; + } + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetVSlotsResponse::TPtr& ev) { + GetVSlotsResponse->Set(std::move(ev)); + if (FallbackToWhiteboard) { + RequestDone(); + return; + } + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetPDisksResponse::TPtr& ev) { + GetPDisksResponse->Set(std::move(ev)); + if (FallbackToWhiteboard) { + RequestDone(); + return; + } + ProcessResponses(); + RequestDone(); + } + + void RequestNodesList() { + if (!NodesInfo.has_value()) { + NodesInfo = MakeRequest(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); + } + } + + void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { + NodesInfo->Set(std::move(ev)); + ui32 maxAllowedNodeId = std::numeric_limits::max(); + TIntrusivePtr dynamicNameserviceConfig = AppData()->DynamicNameserviceConfig; + if (dynamicNameserviceConfig) { + maxAllowedNodeId = dynamicNameserviceConfig->MaxStaticNodeId; + } + for (const auto& ni : NodesInfo->Get()->Nodes) { + if (ni.NodeId <= maxAllowedNodeId) { + SendWhiteboardGroupRequest(ni.NodeId); + } + } + RequestDone(); + } + + void Handle(TEvWhiteboard::TEvBSGroupStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + BSGroupStateResponse[nodeId].Set(std::move(ev)); + BSGroupRequestDone(); + } + + void Handle(TEvWhiteboard::TEvVDiskStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + auto& vDiskStateResponse = VDiskStateResponse[nodeId]; + vDiskStateResponse.Set(std::move(ev)); + for (const NKikimrWhiteboard::TVDiskStateInfo& info : vDiskStateResponse->Record.GetVDiskStateInfo()) { + for (const auto& vSlotId : info.GetDonors()) { + SendWhiteboardDisksRequest(vSlotId.GetNodeId()); + } + } + VDiskRequestDone(); + } + + void Handle(TEvWhiteboard::TEvPDiskStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + PDiskStateResponse[nodeId].Set(std::move(ev)); + PDiskRequestDone(); + } + + void ProcessWhiteboardGroups() { + std::unordered_map latestGroupInfo; + for (const auto& [nodeId, bsGroupStateResponse] : BSGroupStateResponse) { + if (bsGroupStateResponse.IsOk()) { + for (const NKikimrWhiteboard::TBSGroupStateInfo& info : bsGroupStateResponse->Record.GetBSGroupStateInfo()) { + TString storagePoolName = info.GetStoragePoolName(); + if (storagePoolName.empty()) { + continue; + } + if (info.VDiskNodeIdsSize() == 0) { + continue; + } + auto itLatest = latestGroupInfo.find(info.GetGroupID()); + if (itLatest == latestGroupInfo.end()) { + latestGroupInfo.emplace(info.GetGroupID(), &info); + } else { + if (info.GetGroupGeneration() > itLatest->second->GetGroupGeneration()) { + itLatest->second = &info; + } + } + } + } + } + GroupData.reserve(latestGroupInfo.size()); // to keep cache stable after emplace + RebuildGroupsByGroupId(); + size_t capacity = GroupData.capacity(); + for (const auto& [groupId, info] : latestGroupInfo) { + auto itGroup = GroupsByGroupId.find(groupId); + if (itGroup == GroupsByGroupId.end()) { + TGroup& group = GroupData.emplace_back(); + group.GroupId = groupId; + group.GroupGeneration = info->GetGroupGeneration(); + group.Erasure = info->GetErasureSpecies(); + group.ErasureSpecies = TErasureType::ErasureSpeciesByName(group.Erasure); + group.PoolName = info->GetStoragePoolName(); + group.EncryptionMode = info->GetEncryption(); + for (auto nodeId : info->GetVDiskNodeIds()) { + group.VDiskNodeIds.push_back(nodeId); + } + for (auto& vDiskId : info->GetVDiskIds()) { + TVDisk& vDisk = group.VDisks.emplace_back(); + vDisk.VDiskId = VDiskIDFromVDiskID(vDiskId); + } + if (capacity != GroupData.capacity()) { + // we expect to never do this + RebuildGroupsByGroupId(); + capacity = GroupData.capacity(); + } + } else { + TGroup& group = *itGroup->second; + if (group.VDiskNodeIds.empty()) { + for (auto nodeId : info->GetVDiskNodeIds()) { + group.VDiskNodeIds.push_back(nodeId); + } + } + } + } + for (TGroup& group : GroupData) { + GroupView.emplace_back(&group); + } + FieldsAvailable |= FieldsWbGroups; + FoundGroups = TotalGroups = GroupView.size(); + ApplyEverything(); + if (FieldsNeeded(FieldsWbDisks)) { + std::unordered_set nodeIds; + for (const TGroup* group : GroupView) { + for (const TVDisk& vdisk : group->VDisks) { + TNodeId nodeId = vdisk.VSlotId.NodeId; + if (nodeIds.insert(nodeId).second) { + SendWhiteboardDisksRequest(nodeId); + } + } + for (const TNodeId nodeId : group->VDiskNodeIds) { + if (nodeIds.insert(nodeId).second) { + SendWhiteboardDisksRequest(nodeId); + } + } + } + } + } + + void FillVDiskFromVDiskStateInfo(TVDisk& vDisk, const TVSlotId& vSlotId, const NKikimrWhiteboard::TVDiskStateInfo& info) { + vDisk.VDiskId = VDiskIDFromVDiskID(info.GetVDiskId()); + vDisk.VSlotId = vSlotId; + vDisk.AllocatedSize = info.GetAllocatedSize(); + vDisk.AvailableSize = info.GetAvailableSize(); + vDisk.Read = info.GetReadThroughput(); + vDisk.Write = info.GetWriteThroughput(); + switch (info.GetVDiskState()) { + case NKikimrWhiteboard::EVDiskState::Initial: + case NKikimrWhiteboard::EVDiskState::SyncGuidRecovery: + vDisk.VDiskStatus = NKikimrBlobStorage::EVDiskStatus::INIT_PENDING; + break; + case NKikimrWhiteboard::EVDiskState::LocalRecoveryError: + case NKikimrWhiteboard::EVDiskState::SyncGuidRecoveryError: + case NKikimrWhiteboard::EVDiskState::PDiskError: + vDisk.VDiskStatus = NKikimrBlobStorage::EVDiskStatus::ERROR; + break; + case NKikimrWhiteboard::EVDiskState::OK: + vDisk.VDiskStatus = info.GetReplicated() ? NKikimrBlobStorage::EVDiskStatus::READY : NKikimrBlobStorage::EVDiskStatus::REPLICATING; + break; + } + if (vDisk.VDiskStatus) { + vDisk.Status = NKikimrBlobStorage::EVDiskStatus_Name(*vDisk.VDiskStatus); + } + vDisk.DiskSpace = static_cast(info.GetDiskSpace()); + vDisk.Donor = info.GetDonorMode(); + for (auto& donor : info.GetDonors()) { + vDisk.Donors.emplace_back(donor); + } + } + + void ProcessWhiteboardDisks() { + if (GroupsByGroupId.empty()) { + RebuildGroupsByGroupId(); + } + for (const auto& [nodeId, vDiskStateResponse] : VDiskStateResponse) { + if (vDiskStateResponse.IsOk()) { + for (const NKikimrWhiteboard::TVDiskStateInfo& info : vDiskStateResponse->Record.GetVDiskStateInfo()) { + TVSlotId vSlotId(nodeId, info.GetPDiskId(), info.GetVDiskSlotId()); + VDisksByVSlotId[vSlotId] = &info; + ui32 groupId = info.GetVDiskId().GetGroupID(); + ui32 groupGeneration = info.GetVDiskId().GetGroupGeneration(); + auto itGroup = GroupsByGroupId.find(groupId); + if (itGroup != GroupsByGroupId.end() && itGroup->second->GroupGeneration == groupGeneration) { + TGroup& group = *(itGroup->second); + TVDisk* vDisk = nullptr; + TVDiskID vDiskId = VDiskIDFromVDiskID(info.GetVDiskId()); + for (TVDisk& disk : group.VDisks) { + if (disk.VDiskId.SameDisk(vDiskId)) { + vDisk = &disk; + break; + } + } + if (vDisk == nullptr) { + vDisk = &(group.VDisks.emplace_back()); + vDisk->VDiskId = vDiskId; + } + FillVDiskFromVDiskStateInfo(*vDisk, vSlotId, info); + } + } + } + } + for (const auto& [nodeId, pDiskStateResponse] : PDiskStateResponse) { + if (pDiskStateResponse.IsOk()) { + for (const NKikimrWhiteboard::TPDiskStateInfo& info : pDiskStateResponse->Record.GetPDiskStateInfo()) { + PDisksByPDiskId[TPDiskId(nodeId, info.GetPDiskId())] = &info; + TPDisk& pDisk = PDisks[{nodeId, info.GetPDiskId()}]; + pDisk.PDiskId = info.GetPDiskId(); + pDisk.NodeId = nodeId; + //pDisk.Type = info.GetType(); + //pDisk.Kind = info.GetKind(); + pDisk.Path = info.GetPath(); + pDisk.Guid = info.GetGuid(); + pDisk.AvailableSize = info.GetAvailableSize(); + pDisk.TotalSize = info.GetTotalSize(); + //pDisk.Status = info.GetStatus(); + if (pDisk.EnforcedDynamicSlotSize < info.GetEnforcedDynamicSlotSize()) { + pDisk.EnforcedDynamicSlotSize = info.GetEnforcedDynamicSlotSize(); + } + if (pDisk.ExpectedSlotCount < info.GetExpectedSlotCount()) { + pDisk.ExpectedSlotCount = info.GetExpectedSlotCount(); + } + if (pDisk.NumActiveSlots < info.GetNumActiveSlots()) { + pDisk.NumActiveSlots = info.GetNumActiveSlots(); + } + pDisk.SetCategory(info.GetCategory()); + //pDisk.DecommitStatus = info.GetDecommitStatus(); + float usage = pDisk.TotalSize ? 100.0 * (pDisk.TotalSize - pDisk.AvailableSize) / pDisk.TotalSize : 0; + if (usage >= 95) { + pDisk.DiskSpace = NKikimrViewer::EFlag::Red; + } else if (usage >= 90) { + pDisk.DiskSpace = NKikimrViewer::EFlag::Orange; + } else if (usage >= 85) { + pDisk.DiskSpace = NKikimrViewer::EFlag::Yellow; + } else { + pDisk.DiskSpace = NKikimrViewer::EFlag::Green; + } + } + } + } + FieldsAvailable |= FieldsWbDisks; + for (TGroup* group : GroupView) { + group->CalcReadWrite(); + } + ApplyEverything(); + if (FieldsNeeded(FieldsGroupState)) { + for (TGroup* group : GroupView) { + group->CalcState(); + } + FieldsAvailable |= FieldsGroupState; + ApplyEverything(); + } + if (FieldsNeeded(FieldsGroupAvailableAndDiskSpace)) { + for (TGroup* group : GroupView) { + group->CalcAvailableAndDiskSpace(PDisks); + } + FieldsAvailable |= FieldsGroupAvailableAndDiskSpace; + ApplyEverything(); + } + } + + void BSGroupRequestDone() { + if (--BSGroupStateRequestsInFlight == 0) { + ProcessWhiteboardGroups(); + } + RequestDone(); + } + + void VDiskRequestDone() { + --VDiskStateRequestsInFlight; + if (VDiskStateRequestsInFlight == 0 && PDiskStateRequestsInFlight == 0) { + ProcessWhiteboardDisks(); + } + RequestDone(); + } + + void PDiskRequestDone() { + --PDiskStateRequestsInFlight; + if (VDiskStateRequestsInFlight == 0 && PDiskStateRequestsInFlight == 0) { + ProcessWhiteboardDisks(); + } + RequestDone(); + } + + void SendWhiteboardGroupRequest(ui32 nodeId) { + if (nodeId == 0) { + return; + } + if (BSGroupStateResponse.count(nodeId) == 0) { + TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); + BSGroupStateResponse.emplace(nodeId, MakeRequest(whiteboardServiceId, + new TEvWhiteboard::TEvBSGroupStateRequest(), + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, + nodeId)); + SubscriptionNodeIds.push_back(nodeId); + ++BSGroupStateRequestsInFlight; + } + } + + void SendWhiteboardDisksRequest(ui32 nodeId) { + if (nodeId == 0) { + return; + } + TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); + if (VDiskStateResponse.count(nodeId) == 0) { + VDiskStateResponse.emplace(nodeId, MakeRequest(whiteboardServiceId, + new TEvWhiteboard::TEvVDiskStateRequest(), + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, + nodeId)); + ++VDiskStateRequestsInFlight; + SubscriptionNodeIds.push_back(nodeId); + } + if (PDiskStateResponse.count(nodeId) == 0) { + PDiskStateResponse.emplace(nodeId, MakeRequest(whiteboardServiceId, + new TEvWhiteboard::TEvPDiskStateRequest(), + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, + nodeId)); + ++PDiskStateRequestsInFlight; + SubscriptionNodeIds.push_back(nodeId); + } + } + + void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { + ui32 nodeId = ev->Get()->NodeId; + { + auto itVDiskStateResponse = VDiskStateResponse.find(nodeId); + if (itVDiskStateResponse != VDiskStateResponse.end()) { + if (itVDiskStateResponse->second.Error("NodeDisconnected")) { + VDiskRequestDone(); + } + } + } + { + auto itPDiskStateResponse = PDiskStateResponse.find(nodeId); + if (itPDiskStateResponse != PDiskStateResponse.end()) { + if (itPDiskStateResponse->second.Error("NodeDisconnected")) { + PDiskRequestDone(); + } + } + } + { + auto itBSGroupStateResponse = BSGroupStateResponse.find(nodeId); + if (itBSGroupStateResponse != BSGroupStateResponse.end()) { + if (itBSGroupStateResponse->second.Error("NodeDisconnected")) { + BSGroupRequestDone(); + } + } + } + } + + void RequestWhiteboard() { + FallbackToWhiteboard = true; + RequestNodesList(); + } + + void OnBscError(const TString& error) { + RequestWhiteboard(); + if (GetGroupsResponse && GetGroupsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (GetStoragePoolsResponse && GetStoragePoolsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (GetVSlotsResponse && GetVSlotsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (GetPDisksResponse && GetPDisksResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + } + + void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { + if (ev->Get()->Status != NKikimrProto::OK) { + TString error = TStringBuilder() << "Failed to establish pipe: " << NKikimrProto::EReplyStatus_Name(ev->Get()->Status); + auto it = HiveStorageStats.find(ev->Get()->TabletId); + if (it != HiveStorageStats.end()) { + if (it->second.Error(error)) { + --HiveStorageStatsInFlight; + ProcessResponses(); + RequestDone(); + } + } + if (ev->Get()->TabletId == GetBSControllerId()) { + AddProblem("bsc-error"); + OnBscError(error); + } + } + } + + STATEFN(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(NSysView::TEvSysView::TEvGetGroupsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetStoragePoolsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetVSlotsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetPDisksResponse, Handle); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + hFunc(TEvHive::TEvResponseHiveStorageStats, Handle); + hFunc(TEvTabletPipe::TEvClientConnected, Handle); + hFunc(TEvents::TEvWakeup, HandleTimeout); + hFunc(TEvInterconnect::TEvNodesInfo, Handle); + hFunc(TEvWhiteboard::TEvVDiskStateResponse, Handle); + hFunc(TEvWhiteboard::TEvPDiskStateResponse, Handle); + hFunc(TEvWhiteboard::TEvBSGroupStateResponse, Handle); + hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); + IgnoreFunc(TEvents::TEvUndelivered/* , Undelivered */); + } + } + + void HandleTimeout(TEvents::TEvWakeup::TPtr& ev) { + switch (ev->Get()->Tag) { + case TimeoutBSC: + if (!AreBSControllerRequestsDone()) { + AddProblem("bsc-timeout"); + OnBscError("timeout"); + } + break; + case TimeoutFinal: + // bread crumbs + if (!AreBSControllerRequestsDone()) { + AddProblem("bsc-incomplete"); + } + if (HiveStorageStatsInFlight > 0) { + AddProblem("hive-incomplete"); + for (auto& hiveStorageStats : HiveStorageStats) { + if (hiveStorageStats.second.Error("timeout")) { + --HiveStorageStatsInFlight; + ProcessResponses(); + //RequestDone(); + } + } + } + if (BSGroupStateRequestsInFlight > 0) { + AddProblem("wb-incomplete-groups"); + ProcessWhiteboardGroups(); + } + if (VDiskStateRequestsInFlight > 0 || PDiskStateRequestsInFlight > 0) { + AddProblem("wb-incomplete-disks"); + ProcessWhiteboardDisks(); + } + ReplyAndPassAway(); + break; + } + } + + void RenderVDisk(NKikimrViewer::TStorageVDisk& jsonVDisk, const TVDisk& vdisk) { + jsonVDisk.SetVDiskId(vdisk.GetVDiskId()); + jsonVDisk.SetNodeId(vdisk.VSlotId.NodeId); + jsonVDisk.SetAllocatedSize(vdisk.AllocatedSize); + jsonVDisk.SetAvailableSize(vdisk.AvailableSize); + jsonVDisk.SetStatus(vdisk.Status); + if (vdisk.DiskSpace != NKikimrViewer::Grey) { + jsonVDisk.SetDiskSpace(vdisk.DiskSpace); + } + auto itVDiskByVSlotId = VDisksByVSlotId.find(vdisk.VSlotId); + if (itVDiskByVSlotId != VDisksByVSlotId.end()) { + auto& whiteboard = *jsonVDisk.MutableWhiteboard(); + whiteboard.CopyFrom(*(itVDiskByVSlotId->second)); + if (whiteboard.GetReplicated() || (whiteboard.GetReplicationProgress() == NAN)) { + whiteboard.ClearReplicationProgress(); + whiteboard.ClearReplicationSecondsRemaining(); + } + } + + auto itPDisk = PDisks.find(vdisk.VSlotId); + if (itPDisk != PDisks.end()) { + const TPDisk& pdisk = itPDisk->second; + NKikimrViewer::TStoragePDisk& jsonPDisk = *jsonVDisk.MutablePDisk(); + jsonPDisk.SetPDiskId(pdisk.GetPDiskId()); + jsonPDisk.SetPath(pdisk.Path); + jsonPDisk.SetType(pdisk.Type); + jsonPDisk.SetGuid(::ToString(pdisk.Guid)); + jsonPDisk.SetCategory(pdisk.Category); + jsonPDisk.SetTotalSize(pdisk.TotalSize); + jsonPDisk.SetAvailableSize(pdisk.AvailableSize); + jsonPDisk.SetStatus(pdisk.Status); + jsonPDisk.SetDecommitStatus(pdisk.DecommitStatus); + jsonPDisk.SetSlotSize(pdisk.GetSlotTotalSize()); + if (pdisk.DiskSpace != NKikimrViewer::Grey) { + jsonPDisk.SetDiskSpace(pdisk.DiskSpace); + } + auto itPDiskByPDiskId = PDisksByPDiskId.find(vdisk.VSlotId); + if (itPDiskByPDiskId != PDisksByPDiskId.end()) { + jsonPDisk.MutableWhiteboard()->CopyFrom(*(itPDiskByPDiskId->second)); + } + } + if (!vdisk.Donors.empty()) { + for (const TVSlotId& donorId : vdisk.Donors) { + NKikimrViewer::TStorageVDisk& jsonDonor = *jsonVDisk.AddDonors(); + TVDisk donor; + auto itVSlotInfo = VSlotsByVSlotId.find(donorId); + if (itVSlotInfo != VSlotsByVSlotId.end()) { + FillVDiskFromVSlotInfo(donor, donorId, *(itVSlotInfo->second)); + } + auto itVDiskInfo = VDisksByVSlotId.find(donorId); + if (itVDiskInfo != VDisksByVSlotId.end()) { + FillVDiskFromVDiskStateInfo(donor, donorId, *(itVDiskInfo->second)); + } + RenderVDisk(jsonDonor, donor); + } + } + } + + void ReplyAndPassAway() override { + AddEvent("ReplyAndPassAway"); + ApplyEverything(); + NKikimrViewer::TStorageGroupsInfo json; + json.SetVersion(Viewer->GetCapabilityVersion("/storage/groups")); + json.SetFieldsAvailable(FieldsAvailable.to_string()); + json.SetFieldsRequired(FieldsRequired.to_string()); + if (NeedFilter) { + json.SetNeedFilter(true); + } + if (NeedGroup) { + json.SetNeedGroup(true); + } + if (NeedSort) { + json.SetNeedSort(true); + } + if (NeedLimit) { + json.SetNeedLimit(true); + } + json.SetTotalGroups(TotalGroups); + json.SetFoundGroups(FoundGroups); + for (auto problem : Problems) { + json.AddProblems(problem); + } + if (GroupGroups.empty()) { + for (const TGroup* group : GroupView) { + NKikimrViewer::TStorageGroupInfo& jsonGroup = *json.AddStorageGroups(); + jsonGroup.SetGroupId(::ToString(group->GroupId)); + if (group->GroupGeneration) { + jsonGroup.SetGroupGeneration(group->GroupGeneration); + } + if (FieldsAvailable.test(+EGroupFields::PoolName)) { + jsonGroup.SetPoolName(group->PoolName); + } + for (const TVDisk& vdisk : group->VDisks) { + RenderVDisk(*jsonGroup.AddVDisks(), vdisk); + } + if (FieldsAvailable.test(+EGroupFields::Encryption)) { + jsonGroup.SetEncryption(group->EncryptionMode); + } + if (group->Overall != NKikimrViewer::Grey) { + jsonGroup.SetOverall(group->Overall); + } + if (group->DiskSpace != NKikimrViewer::Grey) { + jsonGroup.SetDiskSpace(group->DiskSpace); + } + if (FieldsAvailable.test(+EGroupFields::Kind)) { + jsonGroup.SetKind(group->Kind); + } + if (FieldsAvailable.test(+EGroupFields::MediaType)) { + jsonGroup.SetMediaType(group->MediaType); + } + if (FieldsAvailable.test(+EGroupFields::Erasure)) { + jsonGroup.SetErasureSpecies(group->Erasure); + } + if (FieldsAvailable.test(+EGroupFields::AllocationUnits)) { + jsonGroup.SetAllocationUnits(group->AllocationUnits); + } + if (FieldsAvailable.test(+EGroupFields::State)) { + jsonGroup.SetState(group->State); + } + if (FieldsAvailable.test(+EGroupFields::MissingDisks)) { + jsonGroup.SetMissingDisks(group->MissingDisks); + } + if (FieldsAvailable.test(+EGroupFields::Used)) { + jsonGroup.SetUsed(group->Used); + } + if (FieldsAvailable.test(+EGroupFields::Limit)) { + jsonGroup.SetLimit(group->Limit); + } + if (FieldsAvailable.test(+EGroupFields::Read)) { + jsonGroup.SetRead(group->Read); + } + if (FieldsAvailable.test(+EGroupFields::Write)) { + jsonGroup.SetWrite(group->Write); + } + if (FieldsAvailable.test(+EGroupFields::Usage)) { + jsonGroup.SetUsage(group->Usage); + } + if (FieldsAvailable.test(+EGroupFields::Available)) { + jsonGroup.SetAvailable(group->Available); + } + if (FieldsAvailable.test(+EGroupFields::DiskSpaceUsage)) { + jsonGroup.SetDiskSpaceUsage(group->DiskSpaceUsage); + } + if (FieldsAvailable.test(+EGroupFields::Latency)) { + jsonGroup.SetLatencyPutTabletLog(group->PutTabletLogLatency); + jsonGroup.SetLatencyPutUserData(group->PutUserDataLatency); + jsonGroup.SetLatencyGetFast(group->GetFastLatency); + } + } + } else { + for (TGroupGroup& groupGroup : GroupGroups) { + NKikimrViewer::TStorageGroupGroup& jsonGroupGroup = *json.AddStorageGroupGroups(); + jsonGroupGroup.SetGroupName(groupGroup.Name); + jsonGroupGroup.SetGroupCount(groupGroup.Groups.size()); + groupGroup.CalcStats(); + if (FieldsAvailable.test(+EGroupFields::Used)) { + jsonGroupGroup.SetUsed(groupGroup.Used); + } + if (FieldsAvailable.test(+EGroupFields::Limit)) { + jsonGroupGroup.SetLimit(groupGroup.Limit); + } + if (FieldsAvailable.test(+EGroupFields::Read)) { + jsonGroupGroup.SetRead(groupGroup.Read); + } + if (FieldsAvailable.test(+EGroupFields::Write)) { + jsonGroupGroup.SetWrite(groupGroup.Write); + } + if (FieldsAvailable.test(+EGroupFields::Usage)) { + jsonGroupGroup.SetUsage(groupGroup.Usage); + } + if (FieldsAvailable.test(+EGroupFields::Available)) { + jsonGroupGroup.SetAvailable(groupGroup.Available); + } + if (FieldsAvailable.test(+EGroupFields::DiskSpaceUsage)) { + jsonGroupGroup.SetDiskSpaceUsage(groupGroup.DiskSpaceUsage); + } + if (FieldsAvailable.test(+EGroupFields::Latency)) { + jsonGroupGroup.SetLatencyPutTabletLog(groupGroup.PutTabletLogLatency); + jsonGroupGroup.SetLatencyPutUserData(groupGroup.PutUserDataLatency); + jsonGroupGroup.SetLatencyGetFast(groupGroup.GetFastLatency); + } + } + } + AddEvent("RenderingResult"); + TStringStream out; + Proto2Json(json, out, { + .EnumMode = TProto2JsonConfig::EnumValueMode::EnumName, + .StringifyNumbers = TProto2JsonConfig::EStringifyNumbersMode::StringifyInt64Always, + .WriteNanAsString = true, + }); + AddEvent("ResultReady"); + TBase::ReplyAndPassAway(GetHTTPOKJSON(out.Str())); + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - storage + summary: Storage groups + description: Information about storage groups + parameters: + - name: database + in: query + description: database name + required: false + type: string + - name: pool + in: query + description: storage pool name + required: false + type: string + - name: node_id + in: query + description: node id + required: false + type: integer + - name: pdisk_id + in: query + description: pdisk id + required: false + type: integer + - name: group_id + in: query + description: group id + required: false + type: integer + - name: need_groups + in: query + description: return groups information + required: false + type: boolean + default: true + - name: need_disks + in: query + description: return disks information + required: false + type: boolean + default: true + - name: with + in: query + description: > + filter groups by missing or space: + * `missing` + * `space` + required: false + type: string + - name: filter + description: filter to search for in group ids and pool names + required: false + type: string + - name: filter_group_by + in: query + description: > + filter group by: + * `GroupId` + * `Erasure` + * `Usage` + * `DiskSpaceUsage` + * `PoolName` + * `Kind` + * `Encryption` + * `MediaType` + * `MissingDisks` + * `State` + * `Latency` + required: false + type: string + - name: filter_group + in: query + description: content for filter group by + required: false + type: string + - name: sort + in: query + description: > + sort by: + * `PoolName` + * `Kind` + * `MediaType` + * `Erasure` + * `MissingDisks` + * `State` + * `Usage` + * `GroupId` + * `Used` + * `Limit` + * `Usage` + * `Available` + * `DiskSpaceUsage` + * `Encryption` + * `AllocationUnits` + * `Read` + * `Write` + * `Latency` + required: false + type: string + - name: group + in: query + description: > + group by: + * `GroupId` + * `Erasure` + * `Usage` + * `DiskSpaceUsage` + * `PoolName` + * `Kind` + * `Encryption` + * `MediaType` + * `MissingDisks` + * `State` + * `Latency` + required: false + type: string + - name: fields_required + in: query + description: > + list of fields required in response (the more - the heavier could be request): + * `GroupId` (always required) + * `PoolName` + * `Kind` + * `MediaType` + * `Erasure` + * `MissingDisks` + * `State` + * `Usage` + * `Used` + * `Limit` + * `Usage` + * `Available` + * `DiskSpaceUsage` + * `Encryption` + * `AllocationUnits` + * `Read` + * `Write` + * `PDisk` + * `VDisk` + * `Latency` + required: false + type: string + - name: offset + in: query + description: skip N nodes + required: false + type: integer + - name: limit + in: query + description: limit to N nodes + required: false + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + description: format depends on schema parameter + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + YAML::Node schema(node["get"]["responses"]["200"]["content"]["application/json"]["schema"]); + schema = TProtoToYaml::ProtoToYamlSchema(); + YAML::Node properties(schema["properties"]); + properties["Version"]["description"] = "response version (version of the handler)"; + properties["TotalGroups"]["description"] = "total groups found"; + properties["FoundGroups"]["description"] = "number of groups matched the filter"; + properties["NeedFilter"]["description"] = "true if filter couldn't be applied"; + properties["NeedGroup"]["description"] = "true if group by couldn't be applied"; + properties["NeedSort"]["description"] = "true if sort couldn't be applied"; + properties["NeedLimit"]["description"] = "true if limit couldn't be applied"; + properties["Problems"]["description"] = "list of problems collecting the data"; + YAML::Node storageGroupProperties(properties["StorageGroups"]["items"]["properties"]); + storageGroupProperties["State"]["description"] = + "could be one of: \n" + " * `ok` - group is okay\n" + " * `starting:n` - group is okay, but n disks are starting\n" + " * `replicating:n` - group is okay, all disks are available, but n disks are replicating\n" + " * `degraded:n(m, m...)` - group is okay, but n fail realms are not available (with m fail domains)\n" + " * `dead:n` - group is not okay, n fail realms are not available\n"; + storageGroupProperties["Kind"]["description"] = "kind of the disks in this group (specified by the user)"; + storageGroupProperties["MediaType"]["description"] = "actual physical media type of the disks in this group"; + storageGroupProperties["MissingDisks"]["description"] = "number of disks missing"; + storageGroupProperties["AllocationUnits"]["description"] = "number of tablet channels on the group"; + storageGroupProperties["DiskSpace"]["description"] = "disk space status"; + storageGroupProperties["Used"]["description"] = "number of bytes allocated on storage in this group"; + storageGroupProperties["Limit"]["description"] = "number of total bytes (including available) on storage for this group"; + storageGroupProperties["Available"]["description"] = "number of bytes available on storage for this group"; + storageGroupProperties["Usage"]["description"] = "logical usage (in percent) of group space"; + storageGroupProperties["DiskSpaceUsage"]["description"] = "physical usage (in percent) of physical disk space (worst disk)"; + return node; + } +}; + +} diff --git a/ydb/core/viewer/vdisk_blobindexstat.h b/ydb/core/viewer/vdisk_blobindexstat.h new file mode 100644 index 000000000000..37e1aa39ea9e --- /dev/null +++ b/ydb/core/viewer/vdisk_blobindexstat.h @@ -0,0 +1,10 @@ +#pragma once +#include "json_vdisk_req.h" +#include + +namespace NKikimr::NViewer { + +using TJsonBlobIndexStat = TJsonVDiskRequest; + + +} diff --git a/ydb/core/viewer/json_vdisk_evict.h b/ydb/core/viewer/vdisk_evict.h similarity index 70% rename from ydb/core/viewer/json_vdisk_evict.h rename to ydb/core/viewer/vdisk_evict.h index 3af5a956c0b3..624762654ad1 100644 --- a/ydb/core/viewer/json_vdisk_evict.h +++ b/ydb/core/viewer/vdisk_evict.h @@ -1,20 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" +#include "viewer.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonVDiskEvict : public TViewerPipeClient { +class TJsonVDiskEvict : public TViewerPipeClient { enum EEv { EvRetryNodeRequest = EventSpaceBegin(NActors::TEvents::ES_PRIVATE), EvEnd @@ -29,7 +23,7 @@ class TJsonVDiskEvict : public TViewerPipeClient { protected: using TThis = TJsonVDiskEvict; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; IViewer* Viewer; NMon::TEvHttpInfo::TPtr Event; ui32 Timeout = 0; @@ -47,10 +41,6 @@ class TJsonVDiskEvict : public TViewerPipeClient { bool Force = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonVDiskEvict(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Event(ev) @@ -66,7 +56,7 @@ class TJsonVDiskEvict : public TViewerPipeClient { return true; } - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); TString vdisk_id = params.Get("vdisk_id"); if (vdisk_id) { @@ -165,7 +155,7 @@ class TJsonVDiskEvict : public TViewerPipeClient { TBase::PassAway(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { NJson::TJsonValue json; if (Response != nullptr) { if (Response->Record.GetResponse().GetSuccess()) { @@ -188,82 +178,80 @@ class TJsonVDiskEvict : public TViewerPipeClient { TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json)), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - return YAML::Load(R"___( + static YAML::Node GetSwagger() { + return YAML::Load(R"___( post: - tags: - - vdisk - summary: VDisk evict - description: VDisk evict - parameters: - - name: vdisk_id - in: query - description: vdisk identifier - required: false - type: string - - name: group_id - in: query - description: group identifier - required: false - type: integer - - name: group_generation_id - in: query - description: group generation identifier - required: false - type: integer - - name: fail_realm_idx - in: query - description: fail realm identifier - required: false - type: integer - - name: fail_domain_ids - in: query - description: fail domain identifier - required: false - type: integer - - name: vdisk_idx - in: query - description: vdisk idx identifier - required: false - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: force - in: query - description: attempt forced operation, ignore warnings - required: false - type: boolean - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - properties: - result: - type: boolean - description: was operation successful or not - error: - type: string - description: details about failed operation - forceRetryPossible: - type: boolean - description: if true, operation can be retried with force flag - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); -} + tags: + - vdisk + summary: VDisk evict + description: VDisk evict + parameters: + - name: vdisk_id + in: query + description: vdisk identifier + required: false + type: string + - name: group_id + in: query + description: group identifier + required: false + type: integer + - name: group_generation_id + in: query + description: group generation identifier + required: false + type: integer + - name: fail_realm_idx + in: query + description: fail realm identifier + required: false + type: integer + - name: fail_domain_ids + in: query + description: fail domain identifier + required: false + type: integer + - name: vdisk_idx + in: query + description: vdisk idx identifier + required: false + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: force + in: query + description: attempt forced operation, ignore warnings + required: false + type: boolean + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: boolean + description: was operation successful or not + error: + type: string + description: details about failed operation + forceRetryPossible: + type: boolean + description: if true, operation can be retried with force flag + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + } +}; } -} diff --git a/ydb/core/viewer/json_getblob.h b/ydb/core/viewer/vdisk_getblob.h similarity index 80% rename from ydb/core/viewer/json_getblob.h rename to ydb/core/viewer/vdisk_getblob.h index d09c691dcb90..71340249b78e 100644 --- a/ydb/core/viewer/json_getblob.h +++ b/ydb/core/viewer/vdisk_getblob.h @@ -1,14 +1,9 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "json_handlers.h" #include "json_vdisk_req.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using TJsonGetBlob = TJsonVDiskRequest; @@ -79,20 +74,4 @@ struct TJsonVDiskRequestHelper { } }; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Get blob from VDisk"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Get blob from VDisk"; - } -}; - -} } diff --git a/ydb/core/viewer/vdisk_vdiskstat.h b/ydb/core/viewer/vdisk_vdiskstat.h new file mode 100644 index 000000000000..8d71da596369 --- /dev/null +++ b/ydb/core/viewer/vdisk_vdiskstat.h @@ -0,0 +1,9 @@ +#pragma once +#include "json_vdisk_req.h" +#include + +namespace NKikimr::NViewer { + +using TJsonVDiskStat = TJsonVDiskRequest; + +} diff --git a/ydb/core/viewer/viewer.cpp b/ydb/core/viewer/viewer.cpp index 3fe596978c74..b32cc5c0eaad 100644 --- a/ydb/core/viewer/viewer.cpp +++ b/ydb/core/viewer/viewer.cpp @@ -1,81 +1,40 @@ -#include -#include -#include -#include -#include +#include "viewer.h" +#include "counters_hosts.h" +#include "json_handlers.h" +#include "log.h" +#include "viewer_request.h" +#include #include -#include +#include #include +#include +#include +#include #include +#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" -#include "viewer_request.h" -#include "viewer_probes.h" -#include #include -#include "browse_pq.h" -#include "browse_db.h" -#include "counters_hosts.h" -#include "json_healthcheck.h" - -#include "json_handlers.h" - -#include "json_bsgroupinfo.h" -#include "json_nodeinfo.h" -#include "json_vdiskinfo.h" - +#include +#include +#include +#include +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; extern void InitViewerJsonHandlers(TJsonHandlers& jsonHandlers); +extern void InitViewerBrowseJsonHandlers(TJsonHandlers& jsonHandlers); extern void InitPDiskJsonHandlers(TJsonHandlers& jsonHandlers); extern void InitVDiskJsonHandlers(TJsonHandlers& jsonHandlers); extern void InitOperationJsonHandlers(TJsonHandlers& jsonHandlers); +extern void InitQueryJsonHandlers(TJsonHandlers& jsonHandlers); extern void InitSchemeJsonHandlers(TJsonHandlers& jsonHandlers); - -void SetupPQVirtualHandlers(IViewer* viewer) { - viewer->RegisterVirtualHandler( - NKikimrViewer::EObjectType::Root, - [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { - return new NViewerPQ::TBrowseRoot(owner, browseContext); - }); - viewer->RegisterVirtualHandler( - NKikimrViewer::EObjectType::Consumers, - [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { - return new NViewerPQ::TBrowseConsumers(owner, browseContext); - }); - viewer->RegisterVirtualHandler( - NKikimrViewer::EObjectType::Consumer, - [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { - return new NViewerPQ::TBrowseConsumer(owner, browseContext); - }); - viewer->RegisterVirtualHandler( - NKikimrViewer::EObjectType::Topic, - [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { - return new NViewerPQ::TBrowseTopic(owner, browseContext); - }); -} - -void SetupDBVirtualHandlers(IViewer* viewer) { - viewer->RegisterVirtualHandler( - NKikimrViewer::EObjectType::Table, - [] (const TActorId& owner, const IViewer::TBrowseContext& browseContext) -> IActor* { - return new NViewerDB::TBrowseTable(owner, browseContext); - }); -} +extern void InitStorageJsonHandlers(TJsonHandlers& jsonHandlers); class TViewer : public TActorBootstrapped, public IViewer { public: @@ -94,7 +53,6 @@ class TViewer : public TActorBootstrapped, public IViewer { Become(&TThis::StateWork); NActors::TMon* mon = AppData(ctx)->Mon; if (mon) { - NLwTraceMonPage::ProbeRegistry().AddProbesList(LWTRACE_GET_PROBES(VIEWER_PROVIDER)); TVector viewerAllowedSIDs; TVector monitoringAllowedSIDs; { @@ -162,6 +120,13 @@ class TViewer : public TActorBootstrapped, public IViewer { .UseAuth = true, .AllowedSIDs = monitoringAllowedSIDs, }); + mon->RegisterActorPage({ + .RelPath = "query", + .ActorSystem = ctx.ExecutorThread.ActorSystem, + .ActorId = ctx.SelfID, + .UseAuth = true, + .AllowedSIDs = monitoringAllowedSIDs, + }); mon->RegisterActorPage({ .RelPath = "scheme", .ActorSystem = ctx.ExecutorThread.ActorSystem, @@ -169,6 +134,13 @@ class TViewer : public TActorBootstrapped, public IViewer { .UseAuth = true, .AllowedSIDs = viewerAllowedSIDs, }); + mon->RegisterActorPage({ + .RelPath = "storage", + .ActorSystem = ctx.ExecutorThread.ActorSystem, + .ActorId = ctx.SelfID, + .UseAuth = true, + .AllowedSIDs = viewerAllowedSIDs, + }); auto whiteboardServiceId = NNodeWhiteboard::MakeNodeWhiteboardServiceId(ctx.SelfID.NodeId()); ctx.Send(whiteboardServiceId, new NNodeWhiteboard::TEvWhiteboard::TEvSystemStateAddEndpoint( "http-mon", Sprintf(":%d", KikimrRunConfig.AppConfig.GetMonitoringConfig().GetMonitoringPort()))); @@ -178,8 +150,11 @@ class TViewer : public TActorBootstrapped, public IViewer { InitViewerJsonHandlers(JsonHandlers); InitPDiskJsonHandlers(JsonHandlers); InitVDiskJsonHandlers(JsonHandlers); + InitStorageJsonHandlers(JsonHandlers); InitOperationJsonHandlers(JsonHandlers); + InitQueryJsonHandlers(JsonHandlers); InitSchemeJsonHandlers(JsonHandlers); + InitViewerBrowseJsonHandlers(JsonHandlers); for (const auto& handler : JsonHandlers.JsonHandlersList) { // temporary handling of old paths @@ -197,9 +172,6 @@ class TViewer : public TActorBootstrapped, public IViewer { JsonHandlers.JsonHandlersIndex["/viewer/v2/json/nodelist"] = JsonHandlers.JsonHandlersIndex["/viewer/nodelist"]; JsonHandlers.JsonHandlersIndex["/viewer/v2/json/tabletinfo"] = JsonHandlers.JsonHandlersIndex["/viewer/tabletinfo"]; JsonHandlers.JsonHandlersIndex["/viewer/v2/json/nodeinfo"] = JsonHandlers.JsonHandlersIndex["/viewer/nodeinfo"]; - - TWhiteboardInfo::InitMerger(); - TWhiteboardInfo::InitMerger(); } } @@ -305,9 +277,9 @@ class TViewer : public TActorBootstrapped, public IViewer { TString MakeForward(const TRequestState& request, const std::vector& nodes) override { if (nodes.empty()) { - return GetHTTPINTERNALERROR(request, "text/plain", "Couldn't resolve database nodes"); + return GetHTTPBADREQUEST(request, "text/plain", "Couldn't resolve database nodes"); } - if (request->Request.GetUri().StartsWith("/node/")) { + if (!request.Request->Request.GetHeader("X-Forwarded-From-Node").empty()) { return GetHTTPBADREQUEST(request, "text/plain", "Can't do double forward"); } // we expect that nodes order is the same for all requests @@ -346,6 +318,20 @@ class TViewer : public TActorBootstrapped, public IViewer { return {}; } + NJson::TJsonValue GetCapabilities() override { + std::lock_guard guard(JsonHandlersMutex); + NJson::TJsonValue capabilities(NJson::JSON_MAP); + for (const auto& [name, version] : JsonHandlers.Capabilities) { + capabilities[name] = version; + } + return capabilities; + } + + int GetCapabilityVersion(const TString& name) override { + std::lock_guard guard(JsonHandlersMutex); + return JsonHandlers.GetCapabilityVersion(name); + } + void RegisterVirtualHandler( NKikimrViewer::EObjectType parentObjectType, TVirtualHandlerType handler) override { @@ -376,6 +362,7 @@ class TViewer : public TActorBootstrapped, public IViewer { private: TJsonHandlers JsonHandlers; + std::mutex JsonHandlersMutex; std::unordered_map Redirect307; const TKikimrRunConfig KikimrRunConfig; std::unordered_multimap VirtualHandlersByParentType; @@ -395,24 +382,9 @@ class TViewer : public TActorBootstrapped, public IViewer { YAML::Node paths; for (const TString& name : JsonHandlers.JsonHandlersList) { const auto& handler = JsonHandlers.JsonHandlersIndex[name]; - TString tag = TString(TStringBuf(name.substr(1)).Before('/')); - auto path = paths[name]; auto swagger = handler->GetRequestSwagger(); - if (swagger.IsNull()) { - auto get = path["get"]; - get["tags"].push_back(tag); - if (auto summary = handler->GetRequestSummary()) { - get["summary"] = summary; - } - if (auto description = handler->GetRequestDescription()) { - get["description"] = description; - } - get["parameters"] = handler->GetRequestParameters(); - auto responses = get["responses"]; - auto response200 = responses["200"]; - response200["content"]["application/json"]["schema"] = handler->GetResponseJsonSchema(); - } else { - path = swagger; + if (!swagger.IsNull()) { + paths[name] = swagger; } } return paths; @@ -536,12 +508,13 @@ class TViewer : public TActorBootstrapped, public IViewer { } auto handler = JsonHandlers.FindHandler(path); if (handler) { + auto sender(ev->Sender); try { ctx.ExecutorThread.RegisterActor(handler->CreateRequestActor(this, ev)); return; } catch (const std::exception& e) { - ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + Send(sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); return; } } @@ -631,9 +604,10 @@ void TViewer::FillCORS(TStringBuilder& stream, const TRequestState& request) { if (origin) { stream << "Access-Control-Allow-Origin: " << origin << "\r\n" << "Access-Control-Allow-Credentials: true\r\n" - << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept,X-Trace-Verbosity,X-Want-Trace\r\n" - << "Access-Control-Allow-Methods: OPTIONS, GET, POST, DELETE\r\n" - << "Allow: OPTIONS, GET, POST, DELETE\r\n"; + << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept,X-Trace-Verbosity,X-Want-Trace,traceparent\r\n" + << "Access-Control-Expose-Headers: traceresponse,X-Worker-Name\r\n" + << "Access-Control-Allow-Methods: OPTIONS,GET,POST,PUT,DELETE\r\n" + << "Allow: OPTIONS,GET,POST,DELETE\r\n"; } } @@ -909,100 +883,6 @@ NKikimrViewer::EFlag GetVDiskOverallFlag(const NKikimrWhiteboard::TVDiskStateInf return flag; } -TBSGroupState GetBSGroupOverallStateWithoutLatency( - const NKikimrWhiteboard::TBSGroupStateInfo& info, - const TMap& vDisksIndex, - const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { - - TBSGroupState groupState; - groupState.Overall = NKikimrViewer::EFlag::Grey; - - const auto& vDiskIds = info.GetVDiskIds(); - std::unordered_map failedRings; - std::unordered_map failedDomains; - TVector vDiskFlags; - vDiskFlags.reserve(vDiskIds.size()); - for (auto iv = vDiskIds.begin(); iv != vDiskIds.end(); ++iv) { - const NKikimrBlobStorage::TVDiskID& vDiskId = *iv; - NKikimrViewer::EFlag flag = NKikimrViewer::EFlag::Grey; - auto ie = vDisksIndex.find(vDiskId); - if (ie != vDisksIndex.end()) { - auto pDiskId = std::make_pair(ie->second.GetNodeId(), ie->second.GetPDiskId()); - auto ip = pDisksIndex.find(pDiskId); - if (ip != pDisksIndex.end()) { - const NKikimrWhiteboard::TPDiskStateInfo& pDiskInfo(ip->second); - flag = Max(flag, GetPDiskOverallFlag(pDiskInfo)); - } else { - flag = NKikimrViewer::EFlag::Red; - } - const NKikimrWhiteboard::TVDiskStateInfo& vDiskInfo(ie->second); - flag = Max(flag, GetVDiskOverallFlag(vDiskInfo)); - if (vDiskInfo.GetDiskSpace() > NKikimrWhiteboard::EFlag::Green) { - groupState.SpaceProblems++; - } - } else { - flag = NKikimrViewer::EFlag::Red; - } - vDiskFlags.push_back(flag); - if (flag == NKikimrViewer::EFlag::Red || flag == NKikimrViewer::EFlag::Blue) { - groupState.MissingDisks++; - ++failedRings[vDiskId.GetRing()]; - ++failedDomains[vDiskId.GetDomain()]; - } - groupState.Overall = Max(groupState.Overall, flag); - } - - groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); // without failed rings we only allow to raise group status up to Blue/Yellow - TString erasure = info.GetErasureSpecies(); - if (erasure == TErasureType::ErasureSpeciesName(TErasureType::ErasureNone)) { - if (!failedDomains.empty()) { - groupState.Overall = NKikimrViewer::EFlag::Red; - } - } else if (erasure == TErasureType::ErasureSpeciesName(TErasureType::ErasureMirror3dc)) { - if (failedRings.size() > 2) { - groupState.Overall = NKikimrViewer::EFlag::Red; - } else if (failedRings.size() == 2) { // TODO: check for 1 ring - 1 domain rule - groupState.Overall = NKikimrViewer::EFlag::Orange; - } else if (failedRings.size() > 0) { - groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); - } - } else if (erasure == TErasureType::ErasureSpeciesName(TErasureType::Erasure4Plus2Block)) { - if (failedDomains.size() > 2) { - groupState.Overall = NKikimrViewer::EFlag::Red; - } else if (failedDomains.size() > 1) { - groupState.Overall = NKikimrViewer::EFlag::Orange; - } else if (failedDomains.size() > 0) { - groupState.Overall = Min(groupState.Overall, NKikimrViewer::EFlag::Yellow); - } - } - return groupState; -} - -NKikimrViewer::EFlag GetBSGroupOverallFlagWithoutLatency( - const NKikimrWhiteboard::TBSGroupStateInfo& info, - const TMap& vDisksIndex, - const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { - return GetBSGroupOverallStateWithoutLatency(info, vDisksIndex, pDisksIndex).Overall; -} - -TBSGroupState GetBSGroupOverallState( - const NKikimrWhiteboard::TBSGroupStateInfo& info, - const TMap& vDisksIndex, - const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { - TBSGroupState state = GetBSGroupOverallStateWithoutLatency(info, vDisksIndex, pDisksIndex); - if (info.HasLatency()) { - state.Overall = Max(state.Overall, Min(NKikimrViewer::EFlag::Yellow, GetViewerFlag(info.GetLatency()))); - } - return state; -} - -NKikimrViewer::EFlag GetBSGroupOverallFlag( - const NKikimrWhiteboard::TBSGroupStateInfo& info, - const TMap& vDisksIndex, - const TMap, const NKikimrWhiteboard::TPDiskStateInfo&>& pDisksIndex) { - return GetBSGroupOverallState(info, vDisksIndex, pDisksIndex).Overall; -} - NKikimrViewer::EFlag GetViewerFlag(Ydb::Monitoring::StatusFlag::Status flag) { switch (flag) { case Ydb::Monitoring::StatusFlag::GREY: @@ -1058,5 +938,4 @@ NKikimrViewer::EFlag GetViewerFlag(NKikimrWhiteboard::EFlag flag) { return static_cast((int)flag); } -} // NNodeTabletMonitor -} // NKikimr +} diff --git a/ydb/core/viewer/viewer.h b/ydb/core/viewer/viewer.h index 2fe2af350b12..d1f83dbb4932 100644 --- a/ydb/core/viewer/viewer.h +++ b/ydb/core/viewer/viewer.h @@ -1,21 +1,14 @@ #pragma once - -#include -#include - +#include #include -#include +#include +#include #include +#include #include -#include -#include -#include -#include #include -#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { inline TActorId MakeViewerID(ui32 node) { char x[12] = {'v','i','e','w','e','r'}; @@ -55,7 +48,7 @@ struct TRequestSettings { TDuration RetryPeriod = TDuration::MilliSeconds(500); TString Format; std::optional StaticNodesOnly; - bool DistributedMerge = false; + std::vector FieldsRequired; bool Followers = true; // hive tablet info bool Metrics = true; // hive tablet info @@ -206,38 +199,15 @@ class IViewer { virtual void AddRunningQuery(const TString& queryId, const TActorId& actorId) = 0; virtual void EndRunningQuery(const TString& queryId, const TActorId& actorId) = 0; virtual TActorId FindRunningQuery(const TString& queryId) = 0; + + virtual NJson::TJsonValue GetCapabilities() = 0; + virtual int GetCapabilityVersion(const TString& name) = 0; }; void SetupPQVirtualHandlers(IViewer* viewer); void SetupDBVirtualHandlers(IViewer* viewer); void SetupKqpContentHandler(IViewer* viewer); -template -struct TJsonRequestSchema { - static YAML::Node GetSchema() { return {}; } -}; - -template -struct TJsonRequestSummary { - static TString GetSummary() { return {}; } -}; - -template -struct TJsonRequestDescription { - static TString GetDescription() { return {}; } -}; - -template -struct TJsonRequestParameters { - static YAML::Node GetParameters() { return {}; } -}; - -template -struct TJsonRequestSwagger { - static YAML::Node GetSwagger() { return {}; } -}; - - template void GenericSplitIds(TStringBuf source, char delim, OutputIteratorType it) { for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) { @@ -296,8 +266,5 @@ NKikimrViewer::EFlag GetFlagFromUsage(double usage); NKikimrWhiteboard::EFlag GetWhiteboardFlag(NKikimrViewer::EFlag flag); NKikimrViewer::EFlag GetViewerFlag(NKikimrWhiteboard::EFlag flag); -NKikimrViewer::EFlag GetViewerFlag(Ydb::Monitoring::StatusFlag::Status flag); - -} // NViewer -} // NKikimr +} diff --git a/ydb/core/viewer/viewer_acl.h b/ydb/core/viewer/viewer_acl.h new file mode 100644 index 000000000000..91333ef6674e --- /dev/null +++ b/ydb/core/viewer/viewer_acl.h @@ -0,0 +1,274 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" + +namespace NKikimr::NViewer { + +using namespace NActors; + +class TJsonACL : public TViewerPipeClient { + using TThis = TJsonACL; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; + TAutoPtr CacheResult; + TJsonSettings JsonSettings; + bool MergeRules = false; + ui32 Timeout = 0; + +public: + TJsonACL(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) + {} + + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } + const auto& params(Event->Get()->Request.GetParams()); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + if (params.Has("path")) { + RequestSchemeCacheNavigate(params.Get("path")); + } else { + return ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "field 'path' is required")); + } + MergeRules = FromStringWithDefault(params.Get("merge_rules"), MergeRules); + + Become(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } + + STATEFN(StateRequestedDescribe) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + CacheResult = ev->Release(); + RequestDone(); + } + + static bool Has(ui32 accessRights, ui32 mask) { + return (accessRights & mask) == mask; + } + + void FillACE(const NACLibProto::TACE& ace, NKikimrViewer::TMetaCommonInfo::TACE& pbAce) { + if (static_cast(ace.GetAccessType()) == NACLib::EAccessType::Deny) { + pbAce.SetAccessType("Deny"); + } + if (static_cast(ace.GetAccessType()) == NACLib::EAccessType::Allow) { + pbAce.SetAccessType("Allow"); + } + + auto ar = ace.GetAccessRight(); + + static std::pair accessRules[] = { + {NACLib::EAccessRights::GenericFull, "Full"}, + {NACLib::EAccessRights::GenericFullLegacy, "FullLegacy"}, + {NACLib::EAccessRights::GenericManage, "Manage"}, + {NACLib::EAccessRights::GenericUse, "Use"}, + {NACLib::EAccessRights::GenericUseLegacy, "UseLegacy"}, + {NACLib::EAccessRights::GenericWrite, "Write"}, + {NACLib::EAccessRights::GenericRead, "Read"}, + {NACLib::EAccessRights::GenericList, "List"}, + }; + if (MergeRules) { + for (const auto& [rule, name] : accessRules) { + if (Has(ar, rule)) { + pbAce.AddAccessRules(name); + ar &= ~rule; + } + } + } + + static std::pair accessRights[] = { + {NACLib::EAccessRights::SelectRow, "SelectRow"}, + {NACLib::EAccessRights::UpdateRow, "UpdateRow"}, + {NACLib::EAccessRights::EraseRow, "EraseRow"}, + {NACLib::EAccessRights::ReadAttributes, "ReadAttributes"}, + {NACLib::EAccessRights::WriteAttributes, "WriteAttributes"}, + {NACLib::EAccessRights::CreateDirectory, "CreateDirectory"}, + {NACLib::EAccessRights::CreateTable, "CreateTable"}, + {NACLib::EAccessRights::CreateQueue, "CreateQueue"}, + {NACLib::EAccessRights::RemoveSchema, "RemoveSchema"}, + {NACLib::EAccessRights::DescribeSchema, "DescribeSchema"}, + {NACLib::EAccessRights::AlterSchema, "AlterSchema"}, + {NACLib::EAccessRights::CreateDatabase, "CreateDatabase"}, + {NACLib::EAccessRights::DropDatabase, "DropDatabase"}, + {NACLib::EAccessRights::GrantAccessRights, "GrantAccessRights"}, + {NACLib::EAccessRights::WriteUserAttributes, "WriteUserAttributes"}, + {NACLib::EAccessRights::ConnectDatabase, "ConnectDatabase"}, + {NACLib::EAccessRights::ReadStream, "ReadStream"}, + {NACLib::EAccessRights::WriteStream, "WriteStream"}, + {NACLib::EAccessRights::ReadTopic, "ReadTopic"}, + {NACLib::EAccessRights::WriteTopic, "WriteTopic"} + }; + for (const auto& [right, name] : accessRights) { + if (Has(ar, right)) { + pbAce.AddAccessRights(name); + ar &= ~right; + } + } + + if (ar != 0) { + pbAce.AddAccessRights(NACLib::AccessRightsToString(ar)); + } + + pbAce.SetSubject(ace.GetSID()); + + auto inht = ace.GetInheritanceType(); + if ((inht & NACLib::EInheritanceType::InheritObject) != 0) { + pbAce.AddInheritanceType("Object"); + } + if ((inht & NACLib::EInheritanceType::InheritContainer) != 0) { + pbAce.AddInheritanceType("Container"); + } + if ((inht & NACLib::EInheritanceType::InheritOnly) != 0) { + pbAce.AddInheritanceType("Only"); + } + } + + void ReplyAndPassAway() override { + if (CacheResult == nullptr) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "no SchemeCache response")); + } + if (CacheResult->Request == nullptr) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "wrong SchemeCache response")); + } + if (CacheResult->Request.Get()->ResultSet.empty()) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "SchemeCache response is empty")); + } + if (CacheResult->Request.Get()->ErrorCount != 0) { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", TStringBuilder() << "SchemeCache response error " << static_cast(CacheResult->Request.Get()->ResultSet.front().Status))); + } + const auto& entry = CacheResult->Request.Get()->ResultSet.front(); + NKikimrViewer::TMetaInfo metaInfo; + NKikimrViewer::TMetaCommonInfo& pbCommon = *metaInfo.MutableCommon(); + pbCommon.SetPath(CanonizePath(entry.Path)); + if (entry.Self) { + pbCommon.SetOwner(entry.Self->Info.GetOwner()); + if (entry.Self->Info.HasACL()) { + NACLib::TACL acl(entry.Self->Info.GetACL()); + for (const NACLibProto::TACE& ace : acl.GetACE()) { + auto& pbAce = *pbCommon.AddACL(); + FillACE(ace, pbAce); + } + if (acl.GetInterruptInheritance()) { + pbCommon.SetInterruptInheritance(true); + } + } + if (entry.Self->Info.HasEffectiveACL()) { + NACLib::TACL acl(entry.Self->Info.GetEffectiveACL()); + for (const NACLibProto::TACE& ace : acl.GetACE()) { + auto& pbAce = *pbCommon.AddEffectiveACL(); + FillACE(ace, pbAce); + } + } + } + + TStringStream json; + TProtoToJson::ProtoToJson(json, metaInfo, JsonSettings); + + ReplyAndPassAway(GetHTTPOKJSON(json.Str())); + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - viewer + summary: ACL information + description: Returns information about ACL of an object + parameters: + - name: database + in: query + description: database name + type: string + required: false + - name: path + in: query + description: schema path + required: true + type: string + - name: merge_rules + in: query + description: merge access rights into access rules + type: boolean + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + properties: + Common: + type: object + properties: + Path: + type: string + Owner: + type: string + ACL: + type: array + items: + type: object + properties: + AccessType: + type: string + Subject: + type: string + AccessRules: + type: array + items: + type: string + AccessRights: + type: array + items: + type: string + InheritanceType: + type: array + items: + type: string + InterruptInheritance: + type: boolean + EffectiveACL: + type: array + items: + type: object + properties: + AccessType: + type: string + Subject: + type: string + AccessRules: + type: array + items: + type: string + AccessRights: + type: array + items: + type: string + InheritanceType: + type: array + items: + type: string + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + + return node; + } +}; + +} diff --git a/ydb/core/viewer/viewer_autocomplete.h b/ydb/core/viewer/viewer_autocomplete.h new file mode 100644 index 000000000000..069f322532c3 --- /dev/null +++ b/ydb/core/viewer/viewer_autocomplete.h @@ -0,0 +1,380 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" +#include "query_autocomplete_helper.h" +#include "viewer_request.h" + +namespace NKikimr::NViewer { + +using namespace NActors; +using TNavigate = NSchemeCache::TSchemeCacheNavigate; + +class TJsonAutocomplete : public TViewerPipeClient { + using TThis = TJsonAutocomplete; + using TBase = TViewerPipeClient; + TEvViewer::TEvViewerRequest::TPtr ViewerRequest; + TJsonSettings JsonSettings; + ui32 Timeout = 0; + + std::optional> ConsoleResult; + std::optional> CacheResult; + + struct TSchemaWordData { + TString Name; + NKikimrViewer::EAutocompleteType Type; + TString Parent; + + TSchemaWordData(const TString& name, const NKikimrViewer::EAutocompleteType type, const TString& parent = {}) + : Name(name) + , Type(type) + , Parent(parent) + {} + + operator TString() const { + return Name; + } + }; + + TVector DatabasePath; + std::vector Dictionary; + TVector Tables; + TVector Paths; + TString Prefix; + TString SearchWord; + ui32 Limit = 10; + NKikimrViewer::TQueryAutocomplete Result; + +public: + TJsonAutocomplete(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + { + const auto& params(Event->Get()->Request.GetParams()); + InitConfig(params); + ParseCgiParameters(params); + if (IsPostContent()) { + TStringBuf content = Event->Get()->Request.GetPostContent(); + ParsePostContent(content); + } + PrepareParameters(); + } + + // proxied request + TJsonAutocomplete(TEvViewer::TEvViewerRequest::TPtr& ev) + : ViewerRequest(ev) + { + auto& request = ViewerRequest->Get()->Record.GetAutocompleteRequest(); + + Database = request.GetDatabase(); + for (auto& table: request.GetTables()) { + Tables.emplace_back(table); + } + Prefix = request.GetPrefix(); + Limit = request.GetLimit(); + + Timeout = ViewerRequest->Get()->Record.GetTimeout(); + Direct = true; + PrepareParameters(); + } + + void PrepareParameters() { + if (Database) { + DatabasePath = SplitPath(Database); + auto prefixPaths = SplitPath(Prefix); + if (Prefix.EndsWith('/')) { + prefixPaths.emplace_back(); + } + if (!prefixPaths.empty()) { + SearchWord = prefixPaths.back(); + prefixPaths.pop_back(); + } + if (!prefixPaths.empty()) { + Paths.emplace_back(JoinPath(prefixPaths)); + } + for (const TString& table : Tables) { + Paths.emplace_back(table); + } + if (Paths.empty()) { + Paths.emplace_back(); + } + } else { + SearchWord = Prefix; + } + if (Limit == 0) { + Limit = 1000; + } + } + + void ParseCgiParameters(const TCgiParameters& params) { + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + StringSplitter(params.Get("table")).Split(',').SkipEmpty().Collect(&Tables); + Prefix = params.Get("prefix"); + Limit = FromStringWithDefault(params.Get("limit"), Limit); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + } + + void ParsePostContent(const TStringBuf& content) { + NJson::TJsonValue requestData; + bool success = NJson::ReadJsonTree(content, &requestData); + if (success) { + Database = Database.empty() ? requestData["database"].GetStringSafe({}) : Database; + if (requestData["table"].IsArray()) { + for (const auto& table : requestData["table"].GetArraySafe()) { + Tables.emplace_back(table.GetStringSafe()); + } + } + Prefix = Prefix.empty() ? requestData["prefix"].GetStringSafe({}) : Prefix; + if (requestData["limit"].IsDefined()) { + Limit = requestData["limit"].GetInteger(); + } + } + } + + bool IsPostContent() const { + return NViewer::IsPostContent(Event); + } + + TRequestResponse MakeRequestSchemeCacheNavigate() { + auto request = std::make_unique(); + for (const TString& path : Paths) { + Cerr << "Looking into " << path << Endl; + NSchemeCache::TSchemeCacheNavigate::TEntry entry; + entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpList; + entry.SyncVersion = false; + auto splittedPath = SplitPath(path); + entry.Path = DatabasePath; + entry.Path.insert(entry.Path.end(), splittedPath.begin(), splittedPath.end()); + request->ResultSet.emplace_back(entry); + } + return MakeRequest(MakeSchemeCacheID(), + new TEvTxProxySchemeCache::TEvNavigateKeySet(request.release())); + } + + void Bootstrap() override { + if (ViewerRequest) { + // handle proxied request + CacheResult = MakeRequestSchemeCacheNavigate(); + } else { + if (NeedToRedirect()) { + return; + } + if (Database) { + CacheResult = MakeRequestSchemeCacheNavigate(); + } else { + // autocomplete database list via console request + ConsoleResult = MakeRequestConsoleListTenants(); + } + } + + Become(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } + + STATEFN(StateRequestedDescribe) { + switch (ev->GetTypeRewrite()) { + hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void ParseConsoleResult() { + Ydb::Cms::ListDatabasesResult listTenantsResult; + ConsoleResult->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); + for (const TString& path : listTenantsResult.paths()) { + Dictionary.emplace_back(path, NKikimrViewer::ext_sub_domain); + } + } + + static NKikimrViewer::EAutocompleteType ConvertType(TNavigate::EKind navigate) { + switch (navigate) { + case TNavigate::KindSubdomain: + return NKikimrViewer::sub_domain; + case TNavigate::KindPath: + return NKikimrViewer::dir; + case TNavigate::KindExtSubdomain: + return NKikimrViewer::ext_sub_domain; + case TNavigate::KindTable: + return NKikimrViewer::table; + case TNavigate::KindOlapStore: + return NKikimrViewer::column_store; + case TNavigate::KindColumnTable: + return NKikimrViewer::column_table; + case TNavigate::KindRtmr: + return NKikimrViewer::rtmr_volume; + case TNavigate::KindKesus: + return NKikimrViewer::kesus; + case TNavigate::KindSolomon: + return NKikimrViewer::solomon_volume; + case TNavigate::KindTopic: + return NKikimrViewer::pers_queue_group; + case TNavigate::KindCdcStream: + return NKikimrViewer::cdc_stream; + case TNavigate::KindSequence: + return NKikimrViewer::sequence; + case TNavigate::KindReplication: + return NKikimrViewer::replication; + case TNavigate::KindBlobDepot: + return NKikimrViewer::blob_depot; + case TNavigate::KindExternalTable: + return NKikimrViewer::external_table; + case TNavigate::KindExternalDataSource: + return NKikimrViewer::external_data_source; + case TNavigate::KindBlockStoreVolume: + return NKikimrViewer::block_store_volume; + case TNavigate::KindFileStore: + return NKikimrViewer::file_store; + case TNavigate::KindView: + return NKikimrViewer::view; + default: + return NKikimrViewer::dir; + } + } + + void ParseCacheResult() { + NSchemeCache::TSchemeCacheNavigate& navigate = *CacheResult->Get()->Request; + for (auto& entry : navigate.ResultSet) { + if (entry.Status == TSchemeCacheNavigate::EStatus::Ok) { + if (entry.Path.size() >= DatabasePath.size()) { + entry.Path.erase(entry.Path.begin(), entry.Path.begin() + DatabasePath.size()); + } + TString path = JoinPath(entry.Path); + for (const auto& [id, column] : entry.Columns) { + Dictionary.emplace_back(column.Name, NKikimrViewer::column, path); + } + for (const auto& index : entry.Indexes) { + Dictionary.emplace_back(index.GetName(), NKikimrViewer::index, path); + } + for (const auto& cdcStream : entry.CdcStreams) { + Dictionary.emplace_back(cdcStream.GetName(), NKikimrViewer::cdc_stream, path); + } + if (entry.ListNodeEntry) { + for (const auto& child : entry.ListNodeEntry->Children) { + Dictionary.emplace_back(child.Name, ConvertType(child.Kind), path); + } + }; + } else { + Result.add_error(TStringBuilder() << "Error receiving Navigate response: `" << CanonizePath(entry.Path) << "` has <" << ToString(entry.Status) << "> status"); + } + } + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + CacheResult->Set(std::move(ev)); + RequestDone(); + } + + void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { + ConsoleResult->Set(std::move(ev)); + RequestDone(); + } + + void ReplyAndPassAway() override { + if (Viewer) { + Result.SetVersion(Viewer->GetCapabilityVersion("/viewer/autocomplete")); + } + + if (CacheResult) { + if (CacheResult->IsOk()) { + ParseCacheResult(); + } else { + Result.add_error("Failed to collect information from CacheResult"); + } + } + + if (ConsoleResult) { + if (ConsoleResult->IsOk()) { + ParseConsoleResult(); + } else { + Result.add_error("Failed to collect information from ConsoleResult"); + } + } + + Result.set_success(Result.error_size() == 0); + if (Result.error_size() == 0) { + auto autocomplete = FuzzySearcher::Search(Dictionary, SearchWord, Limit); + Result.MutableResult()->SetTotal(autocomplete.size()); + for (const TSchemaWordData* wordData : autocomplete) { + auto entity = Result.MutableResult()->AddEntities(); + entity->SetName(wordData->Name); + entity->SetType(wordData->Type); + if (wordData->Parent) { + entity->SetParent(wordData->Parent); + } + } + } + + if (ViewerRequest) { + TEvViewer::TEvViewerResponse* viewerResponse = new TEvViewer::TEvViewerResponse(); + viewerResponse->Record.MutableAutocompleteResponse()->CopyFrom(Result); + Send(ViewerRequest->Sender, viewerResponse); + PassAway(); + } else { + TStringStream json; + TProtoToJson::ProtoToJson(json, Result, JsonSettings); + TBase::ReplyAndPassAway(GetHTTPOKJSON(json.Str())); + } + } + + void HandleTimeout() { + if (ViewerRequest) { + Result.add_error("Request timed out"); + ReplyAndPassAway(); + } else { + TBase::ReplyAndPassAway(GetHTTPGATEWAYTIMEOUT()); + } + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Autocomplete information", + .Description = "Returns autocomplete information about objects in the database" + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "table", + .Description = "table list", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "prefix", + .Description = "known part of the word", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "limit", + .Description = "limit of entities", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "direct", + .Description = "force execution on current node", + .Type = "boolean", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/json_browse.h b/ydb/core/viewer/viewer_browse.h similarity index 80% rename from ydb/core/viewer/json_browse.h rename to ydb/core/viewer/viewer_browse.h index 223e91571b6a..82bc218c97e6 100644 --- a/ydb/core/viewer/json_browse.h +++ b/ydb/core/viewer/viewer_browse.h @@ -1,21 +1,10 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include #include "browse.h" #include "browse_db.h" -#include "browse_pq.h" -#include +#include "json_handlers.h" #include "viewer.h" #include "wb_aggregate.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -199,56 +188,37 @@ class TJsonBrowse : public TActorBootstrapped { html << request << Endl; } } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: true - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Schema information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns brief information about schema object"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "obsolete", + .Summary = "Schema information", + .Description = "Returns brief information about schema object" + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + .Required = true + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean" + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean" + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer" + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; } -} diff --git a/ydb/core/viewer/viewer_bscontrollerinfo.h b/ydb/core/viewer/viewer_bscontrollerinfo.h new file mode 100644 index 000000000000..95b7c51be01a --- /dev/null +++ b/ydb/core/viewer/viewer_bscontrollerinfo.h @@ -0,0 +1,88 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "viewer.h" + +namespace NKikimr::NViewer { + +using namespace NActors; + +class TJsonBSControllerInfo : public TViewerPipeClient { + using TThis = TJsonBSControllerInfo; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; + TAutoPtr ControllerInfo; + TJsonSettings JsonSettings; + ui32 Timeout = 0; + +public: + TJsonBSControllerInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) + {} + + void Bootstrap() override { + const auto& params(Event->Get()->Request.GetParams()); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + InitConfig(params); + RequestBSControllerInfo(); + Become(&TThis::StateRequestedInfo, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } + + STATEFN(StateRequestedInfo) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvBlobStorage::TEvResponseControllerInfo, Handle); + hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); + cFunc(TEvents::TSystem::Wakeup, TBase::HandleTimeout); + } + } + + void Handle(TEvBlobStorage::TEvResponseControllerInfo::TPtr& ev) { + ControllerInfo = ev->Release(); + RequestDone(); + } + + void ReplyAndPassAway() override { + TStringStream json; + if (ControllerInfo != nullptr) { + TProtoToJson::ProtoToJson(json, ControllerInfo->Record); + } else { + json << "null"; + } + ReplyAndPassAway(GetHTTPOKJSON(json.Str())); + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Storage controller information", + .Description = "Returns information about storage controller" + }); + yaml.AddParameter({ + .Name = "controller_id", + .Description = "storage controller identifier (tablet id)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/json_bsgroupinfo.h b/ydb/core/viewer/viewer_bsgroupinfo.h similarity index 75% rename from ydb/core/viewer/json_bsgroupinfo.h rename to ydb/core/viewer/viewer_bsgroupinfo.h index 24c05ffe5d7a..ba8f6a63835c 100644 --- a/ydb/core/viewer/json_bsgroupinfo.h +++ b/ydb/core/viewer/viewer_bsgroupinfo.h @@ -1,14 +1,7 @@ #pragma once -#include -#include -#include -#include -#include -#include "wb_merge.h" #include "json_wb_req.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { template <> struct TWhiteboardInfo { @@ -55,19 +48,4 @@ struct TWhiteboardMergerComparator { using TJsonBSGroupInfo = TJsonWhiteboardRequest; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Storage groups information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about storage groups"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_capabilities.h b/ydb/core/viewer/viewer_capabilities.h new file mode 100644 index 000000000000..3804ecbede94 --- /dev/null +++ b/ydb/core/viewer/viewer_capabilities.h @@ -0,0 +1,28 @@ +#pragma once +#include "json_pipe_req.h" + +namespace NKikimr::NViewer { + +using namespace NActors; + +class TViewerCapabilities : public TViewerPipeClient { +public: + using TThis = TViewerCapabilities; + using TBase = TViewerPipeClient; + + TViewerCapabilities(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + {} + + void Bootstrap() override { + ReplyAndPassAway(); + } + + void ReplyAndPassAway() override { + NJson::TJsonValue json; + json["Capabilities"] = Viewer->GetCapabilities(); + TBase::ReplyAndPassAway(GetHTTPOKJSON(json)); + } +}; + +} diff --git a/ydb/core/viewer/viewer_check_access.h b/ydb/core/viewer/viewer_check_access.h new file mode 100644 index 000000000000..3a36cc64290a --- /dev/null +++ b/ydb/core/viewer/viewer_check_access.h @@ -0,0 +1,194 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" +#include + +namespace NKikimr::NViewer { + +using namespace NActors; +using TNavigate = NSchemeCache::TSchemeCacheNavigate; + +class TCheckAccess : public TViewerPipeClient { + using TThis = TCheckAccess; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; + TAutoPtr CacheResult; + TVector Permissions; + +public: + TCheckAccess(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) + {} + + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } + const auto& params(Event->Get()->Request.GetParams()); + ui32 timeout = FromStringWithDefault(params.Get("timeout"), 10000); + if (params.Has("permissions")) { + Split(params.Get("permissions"), ",", Permissions); + } else { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "field 'permissions' is required")); + } + if (params.Has("path")) { + RequestSchemeCacheNavigate(params.Get("path")); + } else { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "field 'path' is required")); + } + Become(&TThis::StateRequestedNavigate, TDuration::MilliSeconds(timeout), new TEvents::TEvWakeup()); + } + + STATEFN(StateRequestedNavigate) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + CacheResult = ev->Release(); + RequestDone(); + } + + ui32 GetAccessType(const TString& permission) { + TACLAttrs attrs(0); + try { + attrs = ConvertYdbPermissionNameToACLAttrs(permission); + } + catch (const std::exception&) { + } + return attrs.AccessMask; + } + + bool CheckAccessPermission(const NACLib::TSecurityObject* object, const NACLib::TUserToken* token, const TString& permission) { + const auto& kikimrRunConfig = Viewer->GetKikimrRunConfig(); + const auto& securityConfig = kikimrRunConfig.AppConfig.GetDomainsConfig().GetSecurityConfig(); + if (!securityConfig.GetEnforceUserTokenRequirement()) { + if (!securityConfig.GetEnforceUserTokenCheckRequirement() || token == nullptr) { + return true; + } + } + if (token == nullptr) { + return false; + } + if (object == nullptr) { + return false; + } + ui32 access = GetAccessType(permission); + if (access == 0) { + return false; + } + return object->CheckAccess(access, *token); + } + + void ReplyAndPassAway() override { + std::unique_ptr token; + if (Event->Get()->UserToken) { + token = std::make_unique(Event->Get()->UserToken); + } + if (CacheResult == nullptr) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "no SchemeCache response")); + } + if (CacheResult->Request == nullptr) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "wrong SchemeCache response")); + } + if (CacheResult->Request.Get()->ResultSet.empty()) { + return ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "SchemeCache response is empty")); + } + if (CacheResult->Request.Get()->ErrorCount != 0) { + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", TStringBuilder() << "SchemeCache response error " << static_cast(CacheResult->Request.Get()->ResultSet.front().Status))); + } + + + auto object = CacheResult->Request.Get()->ResultSet.front().SecurityObject; + + NJson::TJsonValue json(NJson::JSON_MAP); + + for (const TString& permission : Permissions) { + json[permission] = CheckAccessPermission(object.Get(), token.get(), permission); + } + + ReplyAndPassAway(GetHTTPOKJSON(json)); + } + + void HandleTimeout() { + ReplyAndPassAway(GetHTTPGATEWAYTIMEOUT("text/plain", "Timeout receiving SchemeCache response")); + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - viewer + summary: Check access + description: Check access to the specified path + parameters: + - name: database + in: query + description: database name + type: string + required: true + - name: path + in: query + description: path to check access + type: string + required: true + - name: permissions + in: query + description: permissions to check + required: true + type: array + items: + type: string + enum: + - ydb.database.connect + - ydb.tables.modify + - ydb.tables.read + - ydb.generic.list + - ydb.generic.read + - ydb.generic.write + - ydb.generic.use_legacy + - ydb.generic.use + - ydb.generic.manage + - ydb.generic.full_legacy + - ydb.generic.full + - ydb.database.create + - ydb.database.drop + - ydb.access.grant + - ydb.granular.select_row + - ydb.granular.update_row + - ydb.granular.erase_row + - ydb.granular.read_attributes + - ydb.granular.write_attributes + - ydb.granular.create_directory + - ydb.granular.create_table + - ydb.granular.create_queue + - ydb.granular.remove_schema + - ydb.granular.describe_schema + - ydb.granular.alter_schema + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + + return node; + } +}; + +} diff --git a/ydb/core/viewer/viewer_cluster.h b/ydb/core/viewer/viewer_cluster.h new file mode 100644 index 000000000000..acd2b977d1c7 --- /dev/null +++ b/ydb/core/viewer/viewer_cluster.h @@ -0,0 +1,834 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "viewer.h" +#include "viewer_helper.h" +#include "viewer_tabletinfo.h" +#include + +namespace NKikimr::NViewer { + +using namespace NProtobufJson; +using namespace NActors; +using namespace NNodeWhiteboard; + +class TJsonCluster : public TViewerPipeClient { + using TThis = TJsonCluster; + using TBase = TViewerPipeClient; + std::optional> NodesInfoResponse; + std::optional> NodeStateResponse; + std::optional> ListTenantsResponse; + std::optional> PDisksResponse; + std::optional> StorageStatsResponse; + std::optional> HiveNodeStatsResponse; + + int WhiteboardStateRequestsInFlight = 0; + std::unordered_map> SystemStateResponse; + std::unordered_map> TabletStateResponse; + std::unordered_map> SystemViewerResponse; + std::unordered_map> TabletViewerResponse; + + struct TNode { + TEvInterconnect::TNodeInfo NodeInfo; + NKikimrWhiteboard::TSystemStateInfo SystemState; + TNodeId NodeId; + TString DataCenter; + TSubDomainKey SubDomainKey; + bool Static = false; + bool Connected = false; + bool Disconnected = false; + + int GetCandidateScore() const { + int score = 0; + if (Connected) { + score += 100; + } + if (Static) { + score += 10; + } + return score; + } + }; + + struct TNodeBatch { + std::vector NodesToAskFor; + std::vector NodesToAskAbout; + size_t Offset = 0; + bool HasStaticNodes = false; + + TNodeId ChooseNodeId() { + if (Offset >= NodesToAskFor.size()) { + return 0; + } + return NodesToAskFor[Offset++]->NodeId; + } + }; + + using TNodeData = std::vector; + using TNodeCache = std::unordered_map; + + TNodeData NodeData; + TNodeCache NodeCache; + std::unordered_map NodeBatches; + std::vector Problems; + + void AddProblem(const TString& problem) { + for (const auto& p : Problems) { + if (p == problem) { + return; + } + } + Problems.push_back(problem); + } + + NKikimrViewer::TClusterInfo ClusterInfo; + + std::unordered_set FilterTablets; + bool OffloadMerge = true; + size_t OffloadMergeAttempts = 2; + TTabletId RootHiveId = 0; + TJsonSettings JsonSettings; + ui32 Timeout; + bool Tablets = false; + +public: + TJsonCluster(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) + { + const auto& params(Event->Get()->Request.GetParams()); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + InitConfig(params); + Tablets = FromStringWithDefault(params.Get("tablets"), false); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + OffloadMerge = FromStringWithDefault(params.Get("offload_merge"), OffloadMerge); + OffloadMergeAttempts = FromStringWithDefault(params.Get("offload_merge_attempts"), OffloadMergeAttempts); + } + + void Bootstrap() override { + NodesInfoResponse = MakeRequest(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); + NodeStateResponse = MakeWhiteboardRequest(TActivationContext::ActorSystem()->NodeId, new TEvWhiteboard::TEvNodeStateRequest()); + PDisksResponse = RequestBSControllerPDisks(); + StorageStatsResponse = RequestBSControllerStorageStats(); + ListTenantsResponse = MakeRequestConsoleListTenants(); + if (AppData()->DomainsInfo && AppData()->DomainsInfo->Domain) { + TIntrusivePtr domains = AppData()->DomainsInfo; + ClusterInfo.SetDomain(TStringBuilder() << "/" << AppData()->DomainsInfo->Domain->Name); + if (const auto& domain = domains->Domain) { + for (TTabletId id : domain->Coordinators) { + FilterTablets.insert(id); + } + for (TTabletId id : domain->Mediators) { + FilterTablets.insert(id); + } + for (TTabletId id : domain->TxAllocators) { + FilterTablets.insert(id); + } + FilterTablets.insert(domain->SchemeRoot); + RootHiveId = domains->GetHive(); + FilterTablets.insert(RootHiveId); + HiveNodeStatsResponse = MakeRequestHiveNodeStats(RootHiveId, new TEvHive::TEvRequestHiveNodeStats()); + } + FilterTablets.insert(MakeBSControllerID()); + FilterTablets.insert(MakeDefaultHiveID()); + FilterTablets.insert(MakeCmsID()); + FilterTablets.insert(MakeNodeBrokerID()); + FilterTablets.insert(MakeTenantSlotBrokerID()); + FilterTablets.insert(MakeConsoleID()); + } + Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } + +private: + static constexpr size_t BATCH_SIZE = 200; + + void BuildCandidates(TNodeBatch& batch, std::vector& candidates) { + auto itCandidate = candidates.begin(); + for (; itCandidate != candidates.end() && batch.NodesToAskFor.size() < OffloadMergeAttempts; ++itCandidate) { + batch.NodesToAskFor.push_back(*itCandidate); + } + candidates.erase(candidates.begin(), itCandidate); + for (TNode* node : batch.NodesToAskAbout) { + if (node->Static) { + batch.HasStaticNodes = true; + } + } + } + + void SplitBatch(TNodeBatch& nodeBatch, std::vector& batches) { + std::vector candidates = nodeBatch.NodesToAskAbout; + std::sort(candidates.begin(), candidates.end(), [](TNode* a, TNode* b) { + return a->GetCandidateScore() > b->GetCandidateScore(); + }); + while (nodeBatch.NodesToAskAbout.size() > BATCH_SIZE) { + TNodeBatch newBatch; + size_t splitSize = std::min(BATCH_SIZE, nodeBatch.NodesToAskAbout.size() / 2); + newBatch.NodesToAskAbout.reserve(splitSize); + for (size_t i = 0; i < splitSize; ++i) { + newBatch.NodesToAskAbout.push_back(nodeBatch.NodesToAskAbout.back()); + nodeBatch.NodesToAskAbout.pop_back(); + } + BuildCandidates(newBatch, candidates); + batches.emplace_back(std::move(newBatch)); + } + if (!nodeBatch.NodesToAskAbout.empty()) { + BuildCandidates(nodeBatch, candidates); + batches.emplace_back(std::move(nodeBatch)); + } + } + + std::vector BatchNodes() { + std::vector batches; + if (OffloadMerge) { + std::unordered_map batchSubDomain; + std::unordered_map batchDataCenters; + for (TNode& node : NodeData) { + if (node.Static) { + batchDataCenters[node.DataCenter].NodesToAskAbout.push_back(&node); + } else { + batchSubDomain[node.SubDomainKey].NodesToAskAbout.push_back(&node); + } + } + for (auto& [subDomainKey, nodeBatch] : batchSubDomain) { + if (nodeBatch.NodesToAskAbout.size() == 1) { + TNode* node = nodeBatch.NodesToAskAbout.front(); + batchDataCenters[node->DataCenter].NodesToAskAbout.push_back(node); + } else { + SplitBatch(nodeBatch, batches); + } + } + for (auto& [dataCenter, nodeBatch] : batchDataCenters) { + SplitBatch(nodeBatch, batches); + } + } else { + TNodeBatch nodeBatch; + for (TNode& node : NodeData) { + nodeBatch.NodesToAskAbout.push_back(&node); + } + SplitBatch(nodeBatch, batches); + } + return batches; + } + + bool TimeToAskWhiteboard() { + if (NodesInfoResponse) { + return false; + } + + if (NodeStateResponse) { + return false; + } + + if (ListTenantsResponse) { + return false; + } + + if (PDisksResponse) { + return false; + } + + if (StorageStatsResponse) { + return false; + } + + if (HiveNodeStatsResponse) { + return false; + } + + return true; + } + + void ProcessResponses() { + if (NodesInfoResponse && NodesInfoResponse->IsDone()) { + if (NodesInfoResponse->IsOk()) { + std::unordered_set hosts; + for (const auto& ni : NodesInfoResponse->Get()->Nodes) { + TNode& node = NodeData.emplace_back(); + node.NodeInfo = ni; + node.NodeId = ni.NodeId; + node.Static = ni.IsStatic; + node.DataCenter = ni.Location.GetDataCenterId(); + hosts.insert(ni.Host); + } + for (TNode& node : NodeData) { + NodeCache.emplace(node.NodeInfo.NodeId, &node); + } + ClusterInfo.SetNodesTotal(NodesInfoResponse->Get()->Nodes.size()); + ClusterInfo.SetHosts(hosts.size()); + } else { + AddProblem("no-nodes-info"); + } + NodesInfoResponse.reset(); + } + + if (NodeData.empty()) { + return; + } + + if (NodeStateResponse && NodeStateResponse->IsDone()) { + if (NodeStateResponse->IsOk()) { + for (const auto& nodeStateInfo : NodeStateResponse->Get()->Record.GetNodeStateInfo()) { + if (nodeStateInfo.GetConnected()) { + TNodeId nodeId = FromStringWithDefault(TStringBuf(nodeStateInfo.GetPeerName()).Before(':'), 0); + if (nodeId) { + TNode* node = NodeCache[nodeId]; + if (node) { + node->Connected = true; + } + } + } + } + } else { + AddProblem("no-node-state-info"); + } + NodeStateResponse.reset(); + } + + if (HiveNodeStatsResponse && HiveNodeStatsResponse->IsDone()) { + if (HiveNodeStatsResponse->IsOk()) { + for (const auto& nodeStats : HiveNodeStatsResponse->Get()->Record.GetNodeStats()) { + TNodeId nodeId = nodeStats.GetNodeId(); + TNode* node = NodeCache[nodeId]; + if (node) { + node->SubDomainKey = TSubDomainKey(nodeStats.GetNodeDomain()); + } + } + } else { + AddProblem("no-hive-node-stats"); + } + HiveNodeStatsResponse.reset(); + } + + if (ListTenantsResponse && ListTenantsResponse->IsDone()) { + if (ListTenantsResponse->IsOk()) { + Ydb::Cms::ListDatabasesResult listTenantsResult; + ListTenantsResponse->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); + ClusterInfo.SetTenants(listTenantsResult.paths().size()); + } else { + AddProblem("no-tenants-info"); + } + ListTenantsResponse.reset(); + } + + if (PDisksResponse && PDisksResponse->IsDone()) { + if (PDisksResponse->IsOk()) { + for (const NKikimrSysView::TPDiskEntry& entry : PDisksResponse->Get()->Record.GetEntries()) { + const NKikimrSysView::TPDiskInfo& info = entry.GetInfo(); + (*ClusterInfo.MutableMapStorageTotal())[info.GetType()] += info.GetTotalSize(); + (*ClusterInfo.MutableMapStorageUsed())[info.GetType()] += info.GetTotalSize() - info.GetAvailableSize(); + } + } else { + AddProblem("no-pdisk-info"); + } + PDisksResponse.reset(); + } + + if (StorageStatsResponse && StorageStatsResponse->IsDone()) { + if (StorageStatsResponse->IsOk()) { + for (NKikimrSysView::TStorageStatsEntry& entry : *StorageStatsResponse->Get()->Record.MutableEntries()) { + NKikimrSysView::TStorageStatsEntry& newEntry = (*ClusterInfo.AddStorageStats()) = std::move(entry); + newEntry.ClearPDiskFilterData(); // remove binary data + } + } else { + AddProblem("no-storage-stats"); + } + StorageStatsResponse.reset(); + } + + if (TimeToAskWhiteboard()) { + std::vector batches = BatchNodes(); + SendWhiteboardRequests(batches); + } + } + + void InitSystemWhiteboardRequest(NKikimrWhiteboard::TEvSystemStateRequest* request) { + //request->AddFieldsRequired(-1); + Y_UNUSED(request); + } + + void InitTabletWhiteboardRequest(NKikimrWhiteboard::TEvTabletStateRequest* request) { + //request->AddFieldsRequired(-1); + Y_UNUSED(request); + } + + void SendWhiteboardRequest(TNodeBatch& batch) { + TNodeId nodeId = OffloadMerge ? batch.ChooseNodeId() : 0; + if (nodeId) { + if (SystemViewerResponse.count(nodeId) == 0) { + auto viewerRequest = std::make_unique(); + InitSystemWhiteboardRequest(viewerRequest->Record.MutableSystemRequest()); + viewerRequest->Record.SetTimeout(Timeout / 2); + for (const TNode* node : batch.NodesToAskAbout) { + viewerRequest->Record.MutableLocation()->AddNodeId(node->NodeId); + } + SystemViewerResponse.emplace(nodeId, MakeViewerRequest(nodeId, viewerRequest.release())); + NodeBatches.emplace(nodeId, batch); + ++WhiteboardStateRequestsInFlight; + } + if (batch.HasStaticNodes && TabletViewerResponse.count(nodeId) == 0) { + auto viewerRequest = std::make_unique(); + InitTabletWhiteboardRequest(viewerRequest->Record.MutableTabletRequest()); + viewerRequest->Record.SetTimeout(Timeout / 2); + for (const TNode* node : batch.NodesToAskAbout) { + if (node->Static) { + viewerRequest->Record.MutableLocation()->AddNodeId(node->NodeId); + } + } + if (viewerRequest->Record.GetLocation().NodeIdSize() > 0) { + TabletViewerResponse.emplace(nodeId, MakeViewerRequest(nodeId, viewerRequest.release())); + NodeBatches.emplace(nodeId, batch); + ++WhiteboardStateRequestsInFlight; + } + } + } else { + for (const TNode* node : batch.NodesToAskAbout) { + if (node->Disconnected) { + continue; + } + TNodeId nodeId = node->NodeId; + if (SystemStateResponse.count(nodeId) == 0) { + auto request = new TEvWhiteboard::TEvSystemStateRequest(); + InitSystemWhiteboardRequest(&request->Record); + SystemStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request)); + ++WhiteboardStateRequestsInFlight; + } + if (node->Static) { + if (TabletStateResponse.count(nodeId) == 0) { + auto request = std::make_unique(); + TabletStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request.release())); + ++WhiteboardStateRequestsInFlight; + } + } + } + } + } + + void SendWhiteboardRequests(std::vector& batches) { + for (TNodeBatch& batch : batches) { + SendWhiteboardRequest(batch); + } + } + + void ProcessWhiteboard() { + for (const auto& [responseNodeId, response] : SystemViewerResponse) { + if (response.IsOk()) { + const auto& systemResponse(response.Get()->Record.GetSystemResponse()); + for (auto& systemInfo : systemResponse.GetSystemStateInfo()) { + TNodeId nodeId = systemInfo.GetNodeId(); + TNode* node = NodeCache[nodeId]; + if (node) { + node->SystemState = std::move(systemInfo); + if (!node->DataCenter) { + node->DataCenter = node->SystemState.GetLocation().GetDataCenter(); + } + } + } + } + } + for (auto& [nodeId, response] : SystemStateResponse) { + if (response.IsOk()) { + auto& systemState(response.Get()->Record); + if (systemState.SystemStateInfoSize() > 0) { + TNode* node = NodeCache[nodeId]; + if (node) { + node->SystemState = std::move(*systemState.MutableSystemStateInfo(0)); + if (!node->DataCenter) { + node->DataCenter = node->SystemState.GetLocation().GetDataCenter(); + } + } + } + } + } + std::unordered_map mergedTabletState; + for (auto& [nodeId, response] : TabletViewerResponse) { + if (response.IsOk()) { + auto& tabletResponse(*(response.Get()->Record.MutableTabletResponse())); + for (auto& tabletState : *tabletResponse.MutableTabletStateInfo()) { + NKikimrWhiteboard::TTabletStateInfo& mergedState(mergedTabletState[tabletState.GetTabletId()]); + if (tabletState.GetGeneration() > mergedState.GetGeneration()) { + mergedState = std::move(tabletState); + } + } + } + } + for (auto& [nodeId, response] : TabletStateResponse) { + if (response.IsOk()) { + for (auto& tabletState : *response.Get()->Record.MutableTabletStateInfo()) { + NKikimrWhiteboard::TTabletStateInfo& mergedState(mergedTabletState[tabletState.GetTabletId()]); + if (tabletState.GetGeneration() > mergedState.GetGeneration()) { + mergedState = std::move(tabletState); + } + } + } + } + + for (TNode& node : NodeData) { + const NKikimrWhiteboard::TSystemStateInfo& systemState = node.SystemState; + (*ClusterInfo.MutableMapDataCenters())[node.DataCenter]++; + if (systemState.HasNumberOfCpus()) { + ClusterInfo.SetNumberOfCpus(ClusterInfo.GetNumberOfCpus() + systemState.GetNumberOfCpus()); + } + if (systemState.LoadAverageSize() > 0) { + ClusterInfo.SetLoadAverage(ClusterInfo.GetLoadAverage() + systemState.GetLoadAverage(0)); + } + if (systemState.HasVersion()) { + (*ClusterInfo.MutableMapVersions())[systemState.GetVersion()]++; + } + if (systemState.HasClusterName() && !ClusterInfo.GetName()) { + ClusterInfo.SetName(systemState.GetClusterName()); + } + ClusterInfo.SetMemoryTotal(ClusterInfo.GetMemoryTotal() + systemState.GetMemoryLimit()); + ClusterInfo.SetMemoryUsed(ClusterInfo.GetMemoryUsed() + systemState.GetMemoryUsed()); + if (!node.Disconnected && node.SystemState.HasSystemState()) { + ClusterInfo.SetNodesAlive(ClusterInfo.GetNodesAlive() + 1); + } + (*ClusterInfo.MutableMapNodeStates())[NKikimrWhiteboard::EFlag_Name(node.SystemState.GetSystemState())]++; + for (const TString& role : node.SystemState.GetRoles()) { + (*ClusterInfo.MutableMapNodeRoles())[role]++; + } + for (const auto& poolStat : systemState.GetPoolStats()) { + TString poolName = poolStat.GetName(); + NKikimrWhiteboard::TSystemStateInfo_TPoolStats* targetPoolStat = nullptr; + for (NKikimrWhiteboard::TSystemStateInfo_TPoolStats& ps : *ClusterInfo.MutablePoolStats()) { + if (ps.GetName() == poolName) { + targetPoolStat = &ps; + break; + } + } + if (targetPoolStat == nullptr) { + targetPoolStat = ClusterInfo.AddPoolStats(); + targetPoolStat->SetName(poolName); + } + double poolUsage = targetPoolStat->GetUsage() * targetPoolStat->GetThreads(); + poolUsage += poolStat.GetUsage() * poolStat.GetThreads(); + ui32 poolThreads = targetPoolStat->GetThreads() + poolStat.GetThreads(); + if (poolThreads != 0) { + double threadUsage = poolUsage / poolThreads; + targetPoolStat->SetUsage(threadUsage); + targetPoolStat->SetThreads(poolThreads); + } + ClusterInfo.SetCoresUsed(ClusterInfo.GetCoresUsed() + poolStat.GetUsage() * poolStat.GetThreads()); + } + } + + for (auto& [tabletId, tabletState] : mergedTabletState) { + if (FilterTablets.empty() || FilterTablets.count(tabletId)) { + auto tabletFlag = GetWhiteboardFlag(GetFlagFromTabletState(tabletState.GetState())); + tabletState.SetOverall(tabletFlag); + (*ClusterInfo.AddSystemTablets()) = std::move(tabletState); + } + } + } + + void WhiteboardRequestDone() { + --WhiteboardStateRequestsInFlight; + if (WhiteboardStateRequestsInFlight == 0) { + ProcessWhiteboard(); + } + RequestDone(); + } + + void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { + NodesInfoResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvWhiteboard::TEvNodeStateResponse::TPtr& ev) { + NodeStateResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { + ListTenantsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetPDisksResponse::TPtr& ev) { + PDisksResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetStorageStatsResponse::TPtr& ev) { + StorageStatsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvHive::TEvResponseHiveNodeStats::TPtr& ev) { + HiveNodeStatsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + SystemStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvWhiteboard::TEvTabletStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + TabletStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvViewer::TEvViewerResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + switch (ev->Get()->Record.Response_case()) { + case NKikimrViewer::TEvViewerResponse::ResponseCase::kSystemResponse: + SystemViewerResponse[nodeId].Set(std::move(ev)); + NodeBatches.erase(nodeId); + WhiteboardRequestDone(); + return; + case NKikimrViewer::TEvViewerResponse::ResponseCase::kTabletResponse: + TabletViewerResponse[nodeId].Set(std::move(ev)); + NodeBatches.erase(nodeId); + WhiteboardRequestDone(); + return; + default: + break; + } + TString error("WrongResponse"); + { + auto itSystemViewerResponse = SystemViewerResponse.find(nodeId); + if (itSystemViewerResponse != SystemViewerResponse.end()) { + if (itSystemViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + { + auto itTabletViewerResponse = TabletViewerResponse.find(nodeId); + if (itTabletViewerResponse != TabletViewerResponse.end()) { + if (itTabletViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + } + + void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { + TNodeId nodeId = ev->Get()->NodeId; + TNode* node = NodeCache[nodeId]; + if (node) { + node->Disconnected = true; + } + TString error("NodeDisconnected"); + { + auto itSystemStateResponse = SystemStateResponse.find(nodeId); + if (itSystemStateResponse != SystemStateResponse.end()) { + if (itSystemStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itTabletStateResponse = TabletStateResponse.find(nodeId); + if (itTabletStateResponse != TabletStateResponse.end()) { + if (itTabletStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itSystemViewerResponse = SystemViewerResponse.find(nodeId); + if (itSystemViewerResponse != SystemViewerResponse.end()) { + if (itSystemViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + { + auto itTabletViewerResponse = TabletViewerResponse.find(nodeId); + if (itTabletViewerResponse != TabletViewerResponse.end()) { + if (itTabletViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + } + + void Undelivered(TEvents::TEvUndelivered::TPtr& ev) { + TNodeId nodeId = ev->Sender.NodeId(); + TString error("Undelivered"); + { + auto itSystemViewerResponse = SystemViewerResponse.find(nodeId); + if (itSystemViewerResponse != SystemViewerResponse.end()) { + if (itSystemViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + { + auto itTabletViewerResponse = TabletViewerResponse.find(nodeId); + if (itTabletViewerResponse != TabletViewerResponse.end()) { + if (itTabletViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardRequest(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + } + + bool OnBscError(const TString& error) { + bool result = false; + if (StorageStatsResponse && StorageStatsResponse->Error(error)) { + ProcessResponses(); + result = true; + } + if (PDisksResponse && PDisksResponse->Error(error)) { + ProcessResponses(); + result = true; + } + return result; + } + + void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { + if (ev->Get()->Status != NKikimrProto::OK) { + TString error = TStringBuilder() << "Failed to establish pipe to " << ev->Get()->TabletId << ": " + << NKikimrProto::EReplyStatus_Name(ev->Get()->Status); + if (ev->Get()->TabletId == GetBSControllerId()) { + if (OnBscError(error)) { + AddProblem("bsc-error"); + } + } + if (ev->Get()->TabletId == RootHiveId) { + if (HiveNodeStatsResponse && HiveNodeStatsResponse->Error(error)) { + AddProblem("hive-error"); + ProcessResponses(); + } + } + if (ev->Get()->TabletId == MakeConsoleID()) { + if (ListTenantsResponse && ListTenantsResponse->Error(error)) { + AddProblem("console-error"); + ProcessResponses(); + } + } + } + TBase::Handle(ev); // all RequestDone() are handled by base handler + } + + void HandleTimeout() { + ReplyAndPassAway(); + } + + STATEFN(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvInterconnect::TEvNodesInfo, Handle); + hFunc(TEvWhiteboard::TEvNodeStateResponse, Handle); + hFunc(TEvWhiteboard::TEvSystemStateResponse, Handle); + hFunc(TEvWhiteboard::TEvTabletStateResponse, Handle); + hFunc(TEvViewer::TEvViewerResponse, Handle); + hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetPDisksResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetStorageStatsResponse, Handle); + hFunc(TEvHive::TEvResponseHiveNodeStats, Handle); + hFunc(TEvents::TEvUndelivered, Undelivered); + hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); + hFunc(TEvTabletPipe::TEvClientConnected, Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void ReplyAndPassAway() override { + ClusterInfo.SetVersion(Viewer->GetCapabilityVersion("/viewer/cluster")); + for (const auto& problem : Problems) { + ClusterInfo.AddProblems(problem); + } + for (const auto& [dataCenter, nodes] : ClusterInfo.GetMapDataCenters()) { + ClusterInfo.AddDataCenters(dataCenter); + } + for (const auto& [version, count] : ClusterInfo.GetMapVersions()) { + ClusterInfo.AddVersions(version); + } + for (const auto& [type, size] : ClusterInfo.GetMapStorageTotal()) { + ClusterInfo.SetStorageTotal(ClusterInfo.GetStorageTotal() + size); + } + for (const auto& [type, size] : ClusterInfo.GetMapStorageUsed()) { + ClusterInfo.SetStorageUsed(ClusterInfo.GetStorageUsed() + size); + } + NKikimrWhiteboard::EFlag worstState = NKikimrWhiteboard::EFlag::Grey; + ui64 worstNodes = 0; + for (NKikimrWhiteboard::EFlag flag = NKikimrWhiteboard::EFlag::Grey; flag <= NKikimrWhiteboard::EFlag::Red; flag = NKikimrWhiteboard::EFlag(flag + 1)) { + auto itNodes = ClusterInfo.GetMapNodeStates().find(NKikimrWhiteboard::EFlag_Name(flag)); + if (itNodes == ClusterInfo.GetMapNodeStates().end()) { + continue; + } + auto& nodes = itNodes->second; + if (nodes > worstNodes / 100) { // only if it's more than 1% of all nodes + worstState = flag; + } + worstNodes += nodes; + } + ClusterInfo.SetOverall(GetViewerFlag(worstState)); + TStringStream out; + Proto2Json(ClusterInfo, out, { + .EnumMode = TProto2JsonConfig::EnumValueMode::EnumName, + .MapAsObject = true, + .StringifyNumbers = TProto2JsonConfig::EStringifyNumbersMode::StringifyInt64Always, + .WriteNanAsString = true, + }); + TBase::ReplyAndPassAway(GetHTTPOKJSON(out.Str())); + } + +public: + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Cluster information", + .Description = "Returns information about cluster" + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "tablets", + .Description = "return system tablets state", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/json_compute.h b/ydb/core/viewer/viewer_compute.h similarity index 88% rename from ydb/core/viewer/json_compute.h rename to ydb/core/viewer/viewer_compute.h index 0d045d700909..19ff4a470cbe 100644 --- a/ydb/core/viewer/json_compute.h +++ b/ydb/core/viewer/viewer_compute.h @@ -1,30 +1,19 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" -#include "viewer_helper.h" +#include "json_handlers.h" #include "json_pipe_req.h" +#include "log.h" +#include "viewer_helper.h" +#include "viewer_tabletinfo.h" #include "wb_aggregate.h" #include "wb_merge.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonCompute : public TViewerPipeClient { - using TBase = TViewerPipeClient; +class TJsonCompute : public TViewerPipeClient { + using TThis = TJsonCompute; + using TBase = TViewerPipeClient; IViewer* Viewer; THashMap TenantByPath; THashMap TenantBySubDomainKey; @@ -80,10 +69,6 @@ class TJsonCompute : public TViewerPipeClient { bool IsNodesListSorted = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonCompute(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Event(ev) @@ -109,7 +94,7 @@ class TJsonCompute : public TViewerPipeClient { return false; } - void Bootstrap(const TActorContext& ) { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); @@ -555,7 +540,7 @@ class TJsonCompute : public TViewerPipeClient { } } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { NKikimrWhiteboard::TEvTabletStateResponse tabletInfo; MergeWhiteboardResponses(tabletInfo, NodeTabletInfo); for (const auto& info : tabletInfo.GetTabletStateInfo()) { @@ -620,91 +605,72 @@ class TJsonCompute : public TViewerPipeClient { Result.AddErrors("Timeout occurred"); ReplyAndPassAway(); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: version - in: query - description: query version (v1, v2) - required: false - type: string - - name: path - in: query - description: schema path - required: false - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: uptime - in: query - description: return only nodes with less uptime in sec. - required: false - type: integer - - name: problems_only - in: query - description: return only problem nodes - required: false - type: boolean - - name: filter - in: query - description: filter nodes by id or host - required: false - type: string - - name: sort - in: query - description: sort by (NodeId,Host,DC,Rack,Version,Uptime,Memory,CPU,LoadAverage) - required: false - type: string - - name: offset - in: query - description: skip N nodes - required: false - type: integer - - name: limit - in: query - description: limit to N nodes - required: false - type: integer - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Compute information", + .Description = "Returns information about compute layer of database", + }); + yaml.AddParameter({ + .Name = "version", + .Description = "query version (v1, v2)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "uptime", + .Description = "return only nodes with less uptime in sec.", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "problems_only", + .Description = "return only problem nodes", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "filter", + .Description = "filter nodes by id or host", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "sort", + .Description = "sort by (NodeId,Host,DC,Rack,Version,Uptime,Memory,CPU,LoadAverage)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "offset", + .Description = "skip N nodes", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "limit", + .Description = "limit to N nodes", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Database compute information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about compute layer of database"; - } -}; - -} } diff --git a/ydb/core/viewer/json_config.h b/ydb/core/viewer/viewer_config.h similarity index 62% rename from ydb/core/viewer/json_config.h rename to ydb/core/viewer/viewer_config.h index e239c82fc4f8..b8e68bcd305e 100644 --- a/ydb/core/viewer/json_config.h +++ b/ydb/core/viewer/viewer_config.h @@ -1,14 +1,10 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "json_handlers.h" #include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -38,28 +34,17 @@ class TJsonConfig : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Configuration"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Configuration", + .Description = "Returns configuration", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns configuration"; - } -}; - -} } diff --git a/ydb/core/viewer/json_content.h b/ydb/core/viewer/viewer_content.h similarity index 70% rename from ydb/core/viewer/json_content.h rename to ydb/core/viewer/viewer_content.h index 0d9399d36022..1ce656981f29 100644 --- a/ydb/core/viewer/json_content.h +++ b/ydb/core/viewer/viewer_content.h @@ -1,17 +1,10 @@ -#pragma once -#include -#include -#include -#include -#include -#include "viewer.h" #include "browse.h" +#include "json_handlers.h" +#include "viewer.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -using ::google::protobuf::FieldDescriptor; class TJsonContent : public TActorBootstrapped { using TThis = TJsonContent; @@ -135,65 +128,52 @@ class TJsonContent : public TActorBootstrapped { Die(ctx); } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: true - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: key - in: query - description: key for positioning - required: false - type: string - - name: limit - in: query - description: rows limit - required: false - type: integer - - name: offset - in: query - description: offset in rows - required: false - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Schema content preview"; - } -}; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Return schema preview"; +public: + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "obsolete", + .Summary = "Schema content preview", + .Description = "Return schema preview" + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "key", + .Description = "key for positioning", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "limit", + .Description = "rows limit", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "offset", + .Description = "offset in rows", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + return yaml; } }; - -} } diff --git a/ydb/core/viewer/json_counters.h b/ydb/core/viewer/viewer_counters.h similarity index 97% rename from ydb/core/viewer/json_counters.h rename to ydb/core/viewer/viewer_counters.h index 8ad296bcbe43..6bb10f92d5ce 100644 --- a/ydb/core/viewer/json_counters.h +++ b/ydb/core/viewer/viewer_counters.h @@ -1,14 +1,12 @@ #pragma once -#include -#include -#include -#include -#include +#include "json_handlers.h" #include "viewer.h" -#include "json_tabletinfo.h" +#include "viewer_bsgroupinfo.h" +#include "viewer_pdiskinfo.h" +#include "viewer_tabletinfo.h" +#include "viewer_vdiskinfo.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using ::google::protobuf::FieldDescriptor; @@ -433,7 +431,17 @@ class TJsonCounters : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "counters", + .Description = "counters", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } }; } -} diff --git a/ydb/core/viewer/json_describe.h b/ydb/core/viewer/viewer_describe.h similarity index 69% rename from ydb/core/viewer/json_describe.h rename to ydb/core/viewer/viewer_describe.h index 276bd41e31e6..f42213dbcbfa 100644 --- a/ydb/core/viewer/json_describe.h +++ b/ydb/core/viewer/viewer_describe.h @@ -1,26 +1,20 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" +#include "log.h" +#include "viewer.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using NSchemeShard::TEvSchemeShard; using TNavigate = NSchemeCache::TSchemeCacheNavigate; -class TJsonDescribe : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; +class TJsonDescribe : public TViewerPipeClient { + using TThis = TJsonDescribe; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; TAutoPtr SchemeShardResult; TAutoPtr CacheResult; TAutoPtr DescribeResult; @@ -30,13 +24,8 @@ class TJsonDescribe : public TViewerPipeClient { int Requests = 0; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonDescribe(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) + TJsonDescribe(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) {} void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) { @@ -58,7 +47,7 @@ class TJsonDescribe : public TViewerPipeClient { record->MutableOptions()->SetReturnPartitioningInfo(FromStringWithDefault(params.Get("partitioning_info"), true)); } - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); @@ -128,8 +117,6 @@ class TJsonDescribe : public TViewerPipeClient { void FillDescription(NKikimrSchemeOp::TDirEntry* descr, ui64 schemeShardId) { descr->SetSchemeshardId(schemeShardId); - descr->SetPathId(InvalidLocalPathId); - descr->SetParentPathId(InvalidLocalPathId); descr->SetCreateFinished(true); descr->SetCreateTxId(0); descr->SetCreateStep(0); @@ -188,11 +175,21 @@ class TJsonDescribe : public TViewerPipeClient { result->SetReason(record.GetReason()); result->SetPath(record.GetPath()); result->MutablePathDescription()->CopyFrom(record.GetPathDescription()); - result->SetPathId(record.GetPathId()); + if (record.GetPathId() != 0 && record.GetPathId() != InvalidLocalPathId) { + result->SetPathId(record.GetPathId()); + } + if (result->MutablePathDescription()->GetSelf().GetPathId() == 0 || result->MutablePathDescription()->GetSelf().GetPathId() == InvalidLocalPathId) { + result->MutablePathDescription()->MutableSelf()->ClearPathId(); + } + if (result->MutablePathDescription()->GetSelf().GetParentPathId() == 0 || result->MutablePathDescription()->GetSelf().GetParentPathId() == InvalidLocalPathId) { + result->MutablePathDescription()->MutableSelf()->ClearParentPathId(); + } result->SetLastExistedPrefixPath(record.GetLastExistedPrefixPath()); result->SetLastExistedPrefixPathId(record.GetLastExistedPrefixPathId()); result->MutableLastExistedPrefixDescription()->CopyFrom(record.GetLastExistedPrefixDescription()); - result->SetPathOwnerId(record.GetPathOwnerId()); + if (record.GetPathOwnerId() != 0 && record.GetPathOwnerId() != InvalidOwnerId) { + result->SetPathOwnerId(record.GetPathOwnerId()); + } result->SetSource(NKikimrViewer::TEvDescribeSchemeInfo::SchemeShard); return result; @@ -205,13 +202,23 @@ class TJsonDescribe : public TViewerPipeClient { TAutoPtr result(new NKikimrViewer::TEvDescribeSchemeInfo()); result->SetPath(path); - result->SetPathId(entry.Self->Info.GetPathId()); - result->SetPathOwnerId(entry.Self->Info.GetSchemeshardId()); - auto* pathDescription = result->MutablePathDescription(); auto* self = pathDescription->MutableSelf(); - - self->CopyFrom(entry.Self->Info); + if (entry.Self) { + self->CopyFrom(entry.Self->Info); + if (self->GetPathId() == 0 || self->GetPathId() == InvalidLocalPathId) { + self->ClearPathId(); + } + if (self->GetParentPathId() == 0 || self->GetParentPathId() == InvalidLocalPathId) { + self->ClearParentPathId(); + } + if (entry.Self->Info.GetPathId() != 0 && entry.Self->Info.GetPathId() != InvalidLocalPathId) { + result->SetPathId(entry.Self->Info.GetPathId()); + } + if (entry.Self->Info.GetSchemeshardId() != 0 && entry.Self->Info.GetSchemeshardId() != InvalidOwnerId) { + result->SetPathOwnerId(entry.Self->Info.GetSchemeshardId()); + } + } FillDescription(self, schemeShardId); if (entry.ListNodeEntry) { @@ -229,7 +236,7 @@ class TJsonDescribe : public TViewerPipeClient { return result; } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { TStringStream json; if (SchemeShardResult != nullptr && SchemeShardResult->GetRecord().GetStatus() == NKikimrScheme::EStatus::StatusSuccess) { DescribeResult = GetSchemeShardDescribeSchemeInfo(); @@ -241,11 +248,19 @@ class TJsonDescribe : public TViewerPipeClient { } } if (DescribeResult != nullptr) { - if (ExpandSubElements) { - if (DescribeResult->HasPathDescription()) { - auto& pathDescription = *DescribeResult->MutablePathDescription(); - if (pathDescription.HasTable()) { - auto& table = *pathDescription.MutableTable(); + if (DescribeResult->HasPathDescription()) { + auto& pathDescription = *DescribeResult->MutablePathDescription(); + if (pathDescription.HasTable()) { + auto& table = *pathDescription.MutableTable(); + for (auto& column : *table.MutableColumns()) { + if (!column.HasFamily()) { + column.SetFamily(0); + } + if (column.GetFamily() == 0 && !column.HasFamilyName()) { + column.SetFamilyName("default"); + } + } + if (ExpandSubElements) { for (auto& tableIndex : table.GetTableIndexes()) { NKikimrSchemeOp::TDirEntry& child = *pathDescription.AddChildren(); child.SetName(tableIndex.GetName()); @@ -266,14 +281,21 @@ class TJsonDescribe : public TViewerPipeClient { PassAway(); return; } + for (auto& child : *DescribeResult->MutablePathDescription()->MutableChildren()) { + if (child.GetPathId() == InvalidLocalPathId) { + child.ClearPathId(); + } + if (child.GetParentPathId() == InvalidLocalPathId) { + child.ClearParentPathId(); + } + } TProtoToJson::ProtoToJson(json, *DescribeResult, JsonSettings); DecodeExternalTableContent(json); } else { json << "null"; } - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + ReplyAndPassAway(GetHTTPOKJSON(json.Str())); } void DecodeExternalTableContent(TStringStream& json) const { @@ -313,111 +335,91 @@ class TJsonDescribe : public TViewerPipeClient { } void HandleTimeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + ReplyAndPassAway(GetHTTPGATEWAYTIMEOUT()); } -}; -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Schema detailed information", + .Description = "Returns detailed information about schema object" + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "schemeshard_id", + .Description = "schemeshard identifier (tablet id)", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "path_id", + .Description = "path id", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "backup", + .Description = "return backup information", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "private", + .Description = "return private tables", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "children", + .Description = "return children", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "boundaries", + .Description = "return boundaries", + .Type = "boolean", + .Default = "false", + }); + yaml.AddParameter({ + .Name = "partition_config", + .Description = "return partition configuration", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "partition_stats", + .Description = "return partitions statistics", + .Type = "boolean", + .Default = "false", + }); + yaml.AddParameter({ + .Name = "partitioning_info", + .Description = "return partitioning information", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: false - type: string - - name: schemeshard_id - in: query - description: schemeshard identifier (tablet id) - required: false - type: integer - - name: path_id - in: query - description: path id - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: backup - in: query - description: return backup information - required: false - type: boolean - default: true - - name: private - in: query - description: return private tables - required: false - type: boolean - default: true - - name: children - in: query - description: return children - required: false - type: boolean - default: true - - name: boundaries - in: query - description: return boundaries - required: false - type: boolean - default: false - - name: partition_config - in: query - description: return partition configuration - required: false - type: boolean - default: true - - name: partition_stats - in: query - description: return partitions statistics - required: false - type: boolean - default: false - - name: partitioning_info - in: query - description: return partitioning information - required: false - type: boolean - default: true - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Schema detailed information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns detailed information about schema object"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_describe_consumer.h b/ydb/core/viewer/viewer_describe_consumer.h new file mode 100644 index 000000000000..9c6e5dcfbe51 --- /dev/null +++ b/ydb/core/viewer/viewer_describe_consumer.h @@ -0,0 +1,89 @@ +#pragma once +#include "json_handlers.h" +#include "json_local_rpc.h" +#include +#include + +namespace NKikimr::NViewer { + +using TDescribeConsumerRpc = TJsonLocalRpc; + +class TJsonDescribeConsumer : public TDescribeConsumerRpc { +public: + using TBase = TDescribeConsumerRpc; + + TJsonDescribeConsumer(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + { + AllowedMethods = {HTTP_METHOD_GET}; + } + + void Bootstrap() override { + const auto& params(Event->Get()->Request.GetParams()); + if (params.Has("database_path")) { + Database = params.Get("database_path"); + } + TBase::Bootstrap(); + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - viewer + summary: Topic schema detailed information + description: Returns detailed information about topic + parameters: + - name: database + in: query + description: database name + required: true + type: string + - name: consumer + in: query + description: consumer name + required: true + type: string + - name: include_stats + in: query + description: include stat flag + required: false + type: bool + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: enums + in: query + description: convert enums to strings + required: false + type: boolean + - name: ui64 + in: query + description: return ui64 as number + required: false + type: boolean + responses: + 200: + description: OK + content: + application/json: + schema: {} + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; + } +}; + +} diff --git a/ydb/core/viewer/viewer_describe_topic.h b/ydb/core/viewer/viewer_describe_topic.h new file mode 100644 index 000000000000..007526f0f416 --- /dev/null +++ b/ydb/core/viewer/viewer_describe_topic.h @@ -0,0 +1,76 @@ +#include "json_handlers.h" +#include "json_local_rpc.h" +#include +#include + +namespace NKikimr::NViewer { + +using TDescribeTopicRpc = TJsonLocalRpc; + +class TJsonDescribeTopic : public TDescribeTopicRpc { +public: + using TBase = TDescribeTopicRpc; + + TJsonDescribeTopic(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + { + AllowedMethods = {HTTP_METHOD_GET}; + } + + void Bootstrap() override { + const auto& params(Event->Get()->Request.GetParams()); + if (params.Has("database_path")) { + Database = params.Get("database_path"); + } + TBase::Bootstrap(); + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Topic schema detailed information", + .Description = "Returns detailed information about topic", + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "include_stats", + .Description = "include stat flag", + .Type = "bool", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Required = false, + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/viewer_feature_flags.h b/ydb/core/viewer/viewer_feature_flags.h new file mode 100644 index 000000000000..65e1035bd38f --- /dev/null +++ b/ydb/core/viewer/viewer_feature_flags.h @@ -0,0 +1,216 @@ +#pragma once +#include "json_pipe_req.h" +#include "viewer.h" +#include + +namespace NKikimr::NViewer { + +using namespace NActors; + +class TJsonFeatureFlags : public TViewerPipeClient { + using TThis = TJsonFeatureFlags; + using TBase = TViewerPipeClient; + TJsonSettings JsonSettings; + ui32 Timeout = 0; + THashSet FilterFeatures; + bool ChangedOnly = false; + TRequestResponse TenantsResponse; + TRequestResponse AllConfigsResponse; + std::unordered_map> PathNameNavigateKeySetResults; + std::unordered_map> PathIdNavigateKeySetResults; + +public: + TJsonFeatureFlags(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + : TViewerPipeClient(viewer, ev) + {} + + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } + const auto& params(Event->Get()->Request.GetParams()); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + StringSplitter(params.Get("features")).Split(',').SkipEmpty().Collect(&FilterFeatures); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + ChangedOnly = FromStringWithDefault(params.Get("changed"), ChangedOnly); + if (Database && DatabaseNavigateResponse) { + PathNameNavigateKeySetResults[Database] = std::move(*DatabaseNavigateResponse); + } else { + TenantsResponse = MakeRequestConsoleListTenants(); + } + AllConfigsResponse = MakeRequestConsoleGetAllConfigs(); + + Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } + + STATEFN(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); + hFunc(NConsole::TEvConsole::TEvGetAllConfigsResponse, Handle); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { + TenantsResponse.Set(std::move(ev)); + if (TenantsResponse.IsOk()) { + Ydb::Cms::ListDatabasesResult listDatabasesResult; + TenantsResponse->Record.GetResponse().operation().result().UnpackTo(&listDatabasesResult); + for (const TString& database : listDatabasesResult.paths()) { + if (PathNameNavigateKeySetResults.count(database) == 0) { + PathNameNavigateKeySetResults[database] = MakeRequestSchemeCacheNavigate(database); + } + } + } + if (PathNameNavigateKeySetResults.empty()) { + if (AppData()->DomainsInfo && AppData()->DomainsInfo->Domain) { + TString domain = "/" + AppData()->DomainsInfo->Domain->Name; + PathNameNavigateKeySetResults[domain] = MakeRequestSchemeCacheNavigate(domain); + } + } + RequestDone(); + } + + void Handle(NConsole::TEvConsole::TEvGetAllConfigsResponse::TPtr& ev) { + AllConfigsResponse.Set(std::move(ev)); + RequestDone(); + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + TString path = GetPath(ev); + if (path) { + auto it = PathNameNavigateKeySetResults.find(path); + if (it != PathNameNavigateKeySetResults.end() && !it->second.IsDone()) { + it->second.Set(std::move(ev)); + if (it->second.IsOk()) { + TSchemeCacheNavigate::TEntry& entry(it->second->Request->ResultSet.front()); + if (entry.DomainInfo) { + if (entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { + TPathId resourceDomainKey(entry.DomainInfo->ResourcesDomainKey); + if (PathIdNavigateKeySetResults.count(resourceDomainKey) == 0) { + PathIdNavigateKeySetResults[resourceDomainKey] = MakeRequestSchemeCacheNavigate(resourceDomainKey); + } + } + } + } + RequestDone(); + return; + } + } + TPathId pathId = GetPathId(ev); + if (pathId) { + auto it = PathIdNavigateKeySetResults.find(pathId); + if (it != PathIdNavigateKeySetResults.end() && !it->second.IsDone()) { + it->second.Set(std::move(ev)); + RequestDone(); + return; + } + } + } + + void ParseFeatureFlags(const NKikimrConfig::TFeatureFlags& featureFlags, NKikimrViewer::TFeatureFlagsConfig::TDatabase& result) { + const google::protobuf::Reflection* reflection = featureFlags.GetReflection(); + const google::protobuf::Descriptor* descriptor = featureFlags.GetDescriptor(); + for (int i = 0; i < descriptor->field_count(); ++i) { + const google::protobuf::FieldDescriptor* field = descriptor->field(i); + if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_BOOL) { + if (FilterFeatures.empty() || FilterFeatures.count(field->name())) { + bool hasField = reflection->HasField(featureFlags, field); + if (ChangedOnly && !hasField) { + continue; + } + auto flag = result.AddFeatureFlags(); + flag->SetName(field->name()); + if (hasField) { + flag->SetCurrent(reflection->GetBool(featureFlags, field)); + } + if (field->has_default_value()) { + flag->SetDefault(field->default_value_bool()); + } + } + } + } + } + + void ParseConfig(const TString& database, + const TRequestResponse& navigate, + NKikimrViewer::TFeatureFlagsConfig& result) { + if (AllConfigsResponse.IsOk()) { + TString realDatabase = database; + auto databaseProto = result.AddDatabases(); + databaseProto->SetName(database); + TSchemeCacheNavigate::TEntry& entry(navigate->Request->ResultSet.front()); + if (entry.DomainInfo) { + if (entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { + TPathId resourceDomainKey(entry.DomainInfo->ResourcesDomainKey); + auto it = PathIdNavigateKeySetResults.find(resourceDomainKey); + if (it != PathIdNavigateKeySetResults.end() && it->second.IsOk() && it->second->Request->ResultSet.size() == 1) { + realDatabase = CanonizePath(it->second->Request->ResultSet.begin()->Path); + } + } + } + NKikimrConfig::TAppConfig appConfig; + if (AllConfigsResponse->Record.GetResponse().config()) { + try { + NYamlConfig::ResolveAndParseYamlConfig(AllConfigsResponse->Record.GetResponse().config(), {}, {{"tenant", realDatabase}}, appConfig); + } catch (const std::exception& e) { + BLOG_ERROR("Failed to parse config for tenant " << realDatabase << ": " << e.what()); + } + ParseFeatureFlags(appConfig.GetFeatureFlags(), *databaseProto); + } else { + ParseFeatureFlags(AppData()->FeatureFlags, *databaseProto); + } + } + } + + void ReplyAndPassAway() override { + // prepare response + NKikimrViewer::TFeatureFlagsConfig Result; + Result.SetVersion(Viewer->GetCapabilityVersion("/viewer/feature_flags")); + for (const auto& [database, navigate] : PathNameNavigateKeySetResults) { + if (navigate.IsOk()) { + ParseConfig(database, navigate, Result); + } + } + TStringStream json; + TProtoToJson::ProtoToJson(json, Result, JsonSettings); + TBase::ReplyAndPassAway(GetHTTPOKJSON(json.Str())); + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Feature flags", + .Description = "Returns feature flags of a database" + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "features", + .Description = "comma separated list of features", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "direct", + .Description = "direct request to the node", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/json_graph.h b/ydb/core/viewer/viewer_graph.h similarity index 60% rename from ydb/core/viewer/json_graph.h rename to ydb/core/viewer/viewer_graph.h index 20fa68980227..de5de41ba1da 100644 --- a/ydb/core/viewer/json_graph.h +++ b/ydb/core/viewer/viewer_graph.h @@ -1,33 +1,27 @@ #pragma once -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" +#include "json_pipe_req.h" #include "log.h" +#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonGraph : public TActorBootstrapped { - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; +class TJsonGraph : public TViewerPipeClient { + using TThis = TJsonGraph; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; std::vector Metrics; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonGraph(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) + TJsonGraph(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) {} - void Bootstrap() { + void Bootstrap() override { BLOG_TRACE("Graph received request for " << Event->Get()->Request.GetUri()); const auto& params(Event->Get()->Request.GetParams()); NKikimrGraph::TEvGetMetrics getRequest; @@ -37,8 +31,7 @@ class TJsonGraph : public TActorBootstrapped { getRequest.AddMetrics(metric); } } else { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPBADREQUEST(Event->Get(), {}, "Bad Request"), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - return PassAway(); + return ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "no 'target' parameter specified")); } if (params.Has("from")) { getRequest.SetTimeFrom(FromStringWithDefault(params.Get("from"))); @@ -57,7 +50,7 @@ class TJsonGraph : public TActorBootstrapped { STATEFN(StateWork) { switch (ev->GetTypeRewrite()) { hFunc(NGraph::TEvGraph::TEvMetricsResult, Handle); - cFunc(TEvents::TSystem::Wakeup, Timeout); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } @@ -69,22 +62,19 @@ class TJsonGraph : public TActorBootstrapped { if (response.GetError()) { json["status"] = "error"; json["error"] = response.GetError(); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false)), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - return PassAway(); + return ReplyAndPassAway(GetHTTPOKJSON(json)); } if (response.DataSize() != Metrics.size()) { json["status"] = "error"; json["error"] = "Invalid data size received"; - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false)), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - return PassAway(); + return ReplyAndPassAway(GetHTTPOKJSON(json)); } for (size_t nMetric = 0; nMetric < response.DataSize(); ++nMetric) { const auto& protoMetric(response.GetData(nMetric)); if (response.TimeSize() != protoMetric.ValuesSize()) { json["status"] = "error"; json["error"] = "Invalid value size received"; - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false)), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - return PassAway(); + return ReplyAndPassAway(GetHTTPOKJSON(json)); } } if (!params.Has("format") || params.Get("format") == "graphite") { // graphite @@ -133,62 +123,46 @@ class TJsonGraph : public TActorBootstrapped { } } - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false)), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + ReplyAndPassAway(GetHTTPOKJSON(json)); } - void Timeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: target - in: query - description: metrics comma delimited - required: true - type: string - - name: from - in: query - description: time in seconds - required: false - type: integer - - name: until - in: query - description: time in seconds - required: false - type: integer - - name: maxDataPoints - in: query - description: maximum number of data points - required: false - type: integer - - name: format - in: query - description: response format, could be prometheus or graphite - required: false - type: string - )___"); - } -}; + void ReplyAndPassAway() override {} -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Graph data"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Graph data", + .Description = "Returns graph data", + }); + yaml.AddParameter({ + .Name = "target", + .Description = "metrics comma delimited", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "from", + .Description = "time in seconds", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "until", + .Description = "time in seconds", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "maxDataPoints", + .Description = "maximum number of data points", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "format", + .Description = "response format, could be prometheus or graphite", + .Type = "string", + }); + return yaml; } }; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns graph data"; - } -}; - -} } diff --git a/ydb/core/viewer/json_healthcheck.h b/ydb/core/viewer/viewer_healthcheck.h similarity index 61% rename from ydb/core/viewer/json_healthcheck.h rename to ydb/core/viewer/viewer_healthcheck.h index 376320d64d40..054c94cc41cd 100644 --- a/ydb/core/viewer/json_healthcheck.h +++ b/ydb/core/viewer/viewer_healthcheck.h @@ -1,35 +1,25 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "healthcheck_record.h" +#include "json_handlers.h" +#include "json_pipe_req.h" #include "viewer.h" -#include -#include -#include #include -#include -#include "json_pipe_req.h" -#include "healthcheck_record.h" -#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; +using namespace NMonitoring; enum HealthCheckResponseFormat { JSON, PROMETHEUS }; -class TJsonHealthCheck : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; +class TJsonHealthCheck : public TViewerPipeClient { + using TThis = TJsonHealthCheck; + using TBase = TViewerPipeClient; static const bool WithRetry = false; - NMon::TEvHttpInfo::TPtr Event; TJsonSettings JsonSettings; ui32 Timeout = 0; HealthCheckResponseFormat Format; @@ -37,17 +27,12 @@ class TJsonHealthCheck : public TViewerPipeClient { bool Cache = true; bool MergeRecords = false; std::optional Result; - std::optional SubscribedNodeId; + std::optional> SelfCheckResult; Ydb::Monitoring::StatusFlag::Status MinStatus = Ydb::Monitoring::StatusFlag::UNSPECIFIED; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonHealthCheck(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Event(ev) + : TViewerPipeClient(viewer, ev) {} THolder MakeSelfCheckRequest() { @@ -71,14 +56,14 @@ class TJsonHealthCheck : public TViewerPipeClient { } void SendHealthCheckRequest() { - auto request = MakeSelfCheckRequest(); - Send(NHealthCheck::MakeHealthCheckID(), request.Release()); + SelfCheckResult = MakeRequest(NHealthCheck::MakeHealthCheckID(), MakeSelfCheckRequest().Release()); } - void Bootstrap() { + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } const auto& params(Event->Get()->Request.GetParams()); - InitConfig(params); - Format = HealthCheckResponseFormat::JSON; if (params.Has("format")) { auto& format = params.Get("format"); @@ -102,14 +87,16 @@ class TJsonHealthCheck : public TViewerPipeClient { JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); } - Database = params.Get("tenant"); + Database = params.Get("database"); + if (Database.empty()) { + Database = params.Get("tenant"); + } Cache = FromStringWithDefault(params.Get("cache"), Cache); MergeRecords = FromStringWithDefault(params.Get("merge_records"), MergeRecords); Timeout = FromStringWithDefault(params.Get("timeout"), 10000); if (params.Get("min_status") && !Ydb::Monitoring::StatusFlag_Status_Parse(params.Get("min_status"), &MinStatus)) { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "The field 'min_status' cannot be parsed"), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - return PassAway(); + return TBase::ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "The field 'min_status' cannot be parsed")); } if (AppData()->FeatureFlags.GetEnableDbMetadataCache() && Cache && Database && MergeRecords) { RequestStateStorageMetadataCacheEndpointsLookup(Database); @@ -120,17 +107,10 @@ class TJsonHealthCheck : public TViewerPipeClient { Become(&TThis::StateRequestedInfo, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); } - void PassAway() override { - if (SubscribedNodeId.has_value()) { - Send(TActivationContext::InterconnectProxy(SubscribedNodeId.value()), new TEvents::TEvUnsubscribe()); - } - TBase::PassAway(); - } - STFUNC(StateRequestedInfo) { switch (ev->GetTypeRewrite()) { hFunc(NHealthCheck::TEvSelfCheckResult, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + cFunc(TEvents::TSystem::Wakeup, TBase::HandleTimeout); hFunc(NHealthCheck::TEvSelfCheckResultProto, Handle); cFunc(TEvents::TSystem::Undelivered, SendHealthCheckRequest); hFunc(TEvStateStorage::TEvBoardInfo, Handle); @@ -163,12 +143,6 @@ class TJsonHealthCheck : public TViewerPipeClient { return MakeHolder>(recordCounters); } - void HandleJSON() { - TStringStream json; - TProtoToJson::ProtoToJson(json, *Result, JsonSettings); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - } - void HandlePrometheus() { auto recordCounters = GetRecordCounters(); @@ -214,22 +188,27 @@ class TJsonHealthCheck : public TViewerPipeClient { e->OnMetricEnd(); e->OnStreamEnd(); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKTEXT(Event->Get()) + ss.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + TBase::ReplyAndPassAway(GetHTTPOK("text/plain", ss.Str())); } - void ReplyAndPassAway() { - if (Result) { - if (Format == HealthCheckResponseFormat::JSON) { - HandleJSON(); - } else { + void ReplyAndPassAway() override { + if (!Result) { + return TBase::ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "No result")); + } else { + if (Format == HealthCheckResponseFormat::PROMETHEUS) { HandlePrometheus(); + return PassAway(); + } else { + TStringStream json; + TProtoToJson::ProtoToJson(json, *Result, JsonSettings); + return TBase::ReplyAndPassAway(GetHTTPOKJSON(json.Str())); } } - PassAway(); } void Handle(NHealthCheck::TEvSelfCheckResult::TPtr& ev) { - Result = std::move(ev->Get()->Result); + SelfCheckResult->Set(std::move(ev)); + Result = std::move(SelfCheckResult->Get()->Result); ReplyAndPassAway(); } @@ -242,99 +221,74 @@ class TJsonHealthCheck : public TViewerPipeClient { void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { auto activeNode = TDatabaseMetadataCache::PickActiveNode(ev->Get()->InfoEntries); if (activeNode != 0) { - SubscribedNodeId = activeNode; - std::optional cache = MakeDatabaseMetadataCacheId(activeNode); + TActorId cache = MakeDatabaseMetadataCacheId(activeNode); auto request = MakeHolder(); - Send(*cache, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, activeNode); + Send(cache, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, activeNode); } else { SendHealthCheckRequest(); } } - void HandleTimeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: tenant - in: query - description: path to database - required: false - type: string - - name: cache - in: query - description: use cache - required: false - type: boolean - - name: verbose - in: query - description: return verbose status - required: false - type: boolean - - name: merge_records - in: query - description: merge records - required: false - type: boolean - - name: max_level - in: query - description: max depth of issues to return - required: false - type: integer - - name: min_status - in: query - description: min status of issues to return - required: false - type: string - - name: format - in: query - description: format of reply - required: false - type: string - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Self-check result"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Self-check result", + .Description = "Performs self-check and returns result", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "cache", + .Description = "use cache", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "verbose", + .Description = "return verbose status", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "merge_records", + .Description = "merge records", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "max_level", + .Description = "max depth of issues to return", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "min_status", + .Description = "min status of issues to return", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "format", + .Description = "format of reply", + .Type = "string", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Performs self-check and returns result"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_helper.h b/ydb/core/viewer/viewer_helper.h index 1d92ac7570f1..01cc486a4c32 100644 --- a/ydb/core/viewer/viewer_helper.h +++ b/ydb/core/viewer/viewer_helper.h @@ -1,7 +1,45 @@ #pragma once - #include +template<> +struct std::hash { + std::size_t operator ()(const NKikimr::TSubDomainKey& s) const { + return s.Hash(); + } +}; + +template <> +struct std::equal_to { + static decltype(auto) make_tuple(const NKikimrBlobStorage::TVDiskID& id) { + return std::make_tuple( + id.GetGroupID(), + id.GetGroupGeneration(), + id.GetRing(), + id.GetDomain(), + id.GetVDisk() + ); + } + + bool operator ()(const NKikimrBlobStorage::TVDiskID& a, const NKikimrBlobStorage::TVDiskID& b) const { + return make_tuple(a) == make_tuple(b); + } +}; + +template <> +struct std::less { + bool operator ()(const NKikimrBlobStorage::TVDiskID& a, const NKikimrBlobStorage::TVDiskID& b) const { + return std::equal_to::make_tuple(a) < std::equal_to::make_tuple(b); + } +}; + +template <> +struct std::hash { + size_t operator ()(const NKikimrBlobStorage::TVDiskID& a) const { + auto tp = std::equal_to::make_tuple(a); + return hash()(tp); + } +}; + namespace NKikimr::NViewer { template void SortCollection(TCollection& collection, TFunc&& func, bool ReverseSort = false) { diff --git a/ydb/core/viewer/viewer_hiveinfo.h b/ydb/core/viewer/viewer_hiveinfo.h new file mode 100644 index 000000000000..197024042701 --- /dev/null +++ b/ydb/core/viewer/viewer_hiveinfo.h @@ -0,0 +1,138 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "viewer.h" + +namespace NKikimr::NViewer { + +using namespace NActors; + +class TJsonHiveInfo : public TViewerPipeClient { + using TThis = TJsonHiveInfo; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; + TAutoPtr HiveInfo; + TJsonSettings JsonSettings; + ui32 Timeout = 0; + TNodeId NodeId = 0; + +public: + TJsonHiveInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) + {} + + void Bootstrap() override { + const auto& params(Event->Get()->Request.GetParams()); + ui64 hiveId = FromStringWithDefault(params.Get("hive_id"), 0); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + NodeId = FromStringWithDefault(params.Get("node"), 0); + InitConfig(params); + if (hiveId != 0 ) { + TAutoPtr request = new TEvHive::TEvRequestHiveInfo(); + if (params.Has("tablet_id")) { + request->Record.SetTabletID(FromStringWithDefault(params.Get("tablet_id"), 0)); + } + if (params.Has("tablet_type")) { + request->Record.SetTabletType(static_cast(FromStringWithDefault(params.Get("tablet_type"), 0))); + } + if (FromStringWithDefault(params.Get("followers"), false)) { + request->Record.SetReturnFollowers(true); + } + if (FromStringWithDefault(params.Get("metrics"), false)) { + request->Record.SetReturnMetrics(true); + } + SendRequestToPipe(ConnectTabletPipe(hiveId), request.Release()); + Become(&TThis::StateRequestedInfo, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + } else { + ReplyAndPassAway(); + } + } + + STATEFN(StateRequestedInfo) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvHive::TEvResponseHiveInfo, Handle); + hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } + } + + void Handle(TEvHive::TEvResponseHiveInfo::TPtr& ev) { + HiveInfo = ev->Release(); + RequestDone(); + } + + void ReplyAndPassAway() override { + TStringStream json; + if (HiveInfo != nullptr) { + if (NodeId != 0) { + for (auto itRecord = HiveInfo->Record.MutableTablets()->begin(); itRecord != HiveInfo->Record.MutableTablets()->end();) { + if (itRecord->GetNodeID() != NodeId) { + itRecord = HiveInfo->Record.MutableTablets()->erase(itRecord); + } else { + ++itRecord; + } + } + } + TProtoToJson::ProtoToJson(json, HiveInfo->Record, JsonSettings); + } else { + json << "null"; + } + ReplyAndPassAway(GetHTTPOKJSON(json.Str())); + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Hive Info", + .Description = "Returns information about the hive", + }); + yaml.AddParameter({ + .Name = "hive_id", + .Description = "hive identifier (tablet id)", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "tablet_id", + .Description = "tablet id filter", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "tablet_type", + .Description = "tablet type filter", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "followers", + .Description = "return followers", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "metrics", + .Description = "return tablet metrics", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; + } +}; + +} diff --git a/ydb/core/viewer/json_hivestats.h b/ydb/core/viewer/viewer_hivestats.h similarity index 50% rename from ydb/core/viewer/json_hivestats.h rename to ydb/core/viewer/viewer_hivestats.h index 483c0e5c120a..80cee5c512a1 100644 --- a/ydb/core/viewer/json_hivestats.h +++ b/ydb/core/viewer/viewer_hivestats.h @@ -1,21 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" -#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonHiveStats : public TViewerPipeClient { - using TBase = TViewerPipeClient; +class TJsonHiveStats : public TViewerPipeClient { + using TThis = TJsonHiveStats; + using TBase = TViewerPipeClient; IViewer* Viewer; NMon::TEvHttpInfo::TPtr Event; TAutoPtr HiveStats; @@ -23,16 +16,12 @@ class TJsonHiveStats : public TViewerPipeClient { ui32 Timeout = 0; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonHiveStats(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) : Viewer(viewer) , Event(ev) {} - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); ui64 hiveId = FromStringWithDefault(params.Get("hive_id"), 0); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); @@ -62,7 +51,7 @@ class TJsonHiveStats : public TViewerPipeClient { RequestDone(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { TStringStream json; if (HiveStats != nullptr) { TProtoToJson::ProtoToJson(json, HiveStats->Record, JsonSettings); @@ -77,66 +66,47 @@ class TJsonHiveStats : public TViewerPipeClient { Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Hive statistics", + .Description = "Returns information about Hive statistics", + }); + yaml.AddParameter({ + .Name = "hive_id", + .Description = "hive identifier (tablet id)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "followers", + .Description = "return followers", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "metrics", + .Description = "return tablet metrics", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: hive_id - in: query - description: hive identifier (tablet id) - required: true - type: string - - name: followers - in: query - description: return followers - required: false - type: boolean - - name: metrics - in: query - description: return tablet metrics - required: false - type: boolean - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Hive statistics"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about Hive statistics"; - } -}; - -} } diff --git a/ydb/core/viewer/json_hotkeys.h b/ydb/core/viewer/viewer_hotkeys.h similarity index 76% rename from ydb/core/viewer/json_hotkeys.h rename to ydb/core/viewer/viewer_hotkeys.h index 055a175b0d6b..db16ce1fc014 100644 --- a/ydb/core/viewer/json_hotkeys.h +++ b/ydb/core/viewer/viewer_hotkeys.h @@ -1,27 +1,18 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using NSchemeShard::TEvSchemeShard; -class TJsonHotkeys : public TViewerPipeClient { +class TJsonHotkeys : public TViewerPipeClient { static const bool WithRetry = false; - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; + using TThis = TJsonHotkeys; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; TAutoPtr DescribeResult; ui32 Timeout = 0; ui32 Limit = 0; @@ -37,13 +28,8 @@ class TJsonHotkeys : public TViewerPipeClient { TMultiSet>, KeysComparator> Keys; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonHotkeys(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) + TJsonHotkeys(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) {} void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) { @@ -53,7 +39,7 @@ class TJsonHotkeys : public TViewerPipeClient { record->MutableOptions()->SetReturnPartitionStats(true); } - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); Timeout = FromStringWithDefault(params.Get("timeout"), 10000); Limit = FromStringWithDefault(params.Get("limit"), 10); @@ -85,6 +71,7 @@ class TJsonHotkeys : public TViewerPipeClient { const auto& pathDescription = pbRecord.GetPathDescription(); const auto& partitions = pathDescription.GetTablePartitions(); const auto& metrics = pathDescription.GetTablePartitionMetrics(); + if (!metrics.empty()) { TVector> tabletsOrder; @@ -143,29 +130,53 @@ class TJsonHotkeys : public TViewerPipeClient { return root; } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { if (DescribeResult != nullptr) { switch (DescribeResult->GetRecord().GetStatus()) { case NKikimrScheme::StatusAccessDenied: - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPFORBIDDEN(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); - return; + return ReplyAndPassAway(GetHTTPFORBIDDEN()); default: break; } } NJson::TJsonValue root = BuildResponse(); - TString json = NJson::WriteJson(root, false); - - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + ReplyAndPassAway(GetHTTPOKJSON(root)); } - void HandleTimeout() { - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Information about current hot keys in a datashard", + .Description = "Samples and returns information about current hot keys", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "path to the table", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enable_sampling", + .Description = "enable sampling", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "polling_factor", + .Description = "polling factor", + .Type = "float", + }); + yaml.AddParameter({ + .Name = "limit", + .Description = "limit of hot keys", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + return yaml; } }; } -} diff --git a/ydb/core/viewer/json_labeledcounters.h b/ydb/core/viewer/viewer_labeled_counters.h similarity index 69% rename from ydb/core/viewer/json_labeledcounters.h rename to ydb/core/viewer/viewer_labeled_counters.h index c8ea014b6205..f2a9272d30c1 100644 --- a/ydb/core/viewer/json_labeledcounters.h +++ b/ydb/core/viewer/viewer_labeled_counters.h @@ -1,15 +1,12 @@ #pragma once -#include -#include -#include -#include +#include "json_handlers.h" +#include "viewer.h" #include -#include #include -#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -47,7 +44,7 @@ class TJsonLabeledCounters : public TActorBootstrapped { Topic = NPersQueue::ConvertNewTopicName(params.Get("topic")); if (Topic.empty()) Topic = "*"; - Consumer = NPersQueue::ConvertNewConsumerName(params.Get("consumer"), ctx); + Consumer = NPersQueue::ConvertNewConsumerName(params.Get("consumer")); DC = params.Get("dc"); if (DC.empty()) DC = "*"; @@ -145,93 +142,71 @@ class TJsonLabeledCounters : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: group - in: query - description: group name - required: false - type: string - - name: dc - in: query - description: datacenter name - required: false - type: string - default: "*" - - name: topic - in: query - description: topic name - required: false - type: string - default: "*" - - name: consumer - in: query - description: consumer name - required: false - type: string - default: "" - - name: group_names - in: query - description: group names - required: false - type: string - - name: counters - in: query - description: counters names - required: false - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - default: false - - name: all - in: query - description: return information about all topics and clients - required: false - type: boolean - default: false - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - default: false - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - default: 10000 - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Labeled counters info", + .Description = "Returns information about labeled counters", + }); + yaml.AddParameter({ + .Name = "group", + .Description = "group name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "dc", + .Description = "datacenter name", + .Type = "string", + .Default = "*", + }); + yaml.AddParameter({ + .Name = "topic", + .Description = "topic name", + .Type = "string", + .Default = "*", + }); + yaml.AddParameter({ + .Name = "consumer", + .Description = "consumer name", + .Type = "string", + .Default = "", + }); + yaml.AddParameter({ + .Name = "group_names", + .Description = "group names", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "counters", + .Description = "counters names", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "all", + .Description = "return information about all topics and clients", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + .Default = "10000", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Labeled counters info"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about labeled counters"; - } -}; - -} } diff --git a/ydb/core/viewer/json_metainfo.h b/ydb/core/viewer/viewer_metainfo.h similarity index 66% rename from ydb/core/viewer/json_metainfo.h rename to ydb/core/viewer/viewer_metainfo.h index b96a26a4478a..dcd7a12629e4 100644 --- a/ydb/core/viewer/json_metainfo.h +++ b/ydb/core/viewer/viewer_metainfo.h @@ -1,25 +1,9 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "browse.h" -#include +#include "json_handlers.h" #include "viewer.h" #include "wb_aggregate.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -124,66 +108,42 @@ class TJsonMetaInfo : public TActorBootstrapped { html << request << Endl; } } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: false - type: string - - name: tablet_id - in: query - description: tablet identifier - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: counters - in: query - description: return tablet counters - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "obsolete", + .Summary = "Schema meta information", + .Description = "Returns meta information about schema path", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "counters", + .Description = "return tablet counters", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Schema meta information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns meta information about schema path"; - } -}; - -} } diff --git a/ydb/core/viewer/json_netinfo.h b/ydb/core/viewer/viewer_netinfo.h similarity index 84% rename from ydb/core/viewer/json_netinfo.h rename to ydb/core/viewer/viewer_netinfo.h index e57e18677801..edddfa3010b8 100644 --- a/ydb/core/viewer/json_netinfo.h +++ b/ydb/core/viewer/viewer_netinfo.h @@ -1,29 +1,17 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" +#include "viewer.h" #include "wb_aggregate.h" #include "wb_merge.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonNetInfo : public TViewerPipeClient { - using TBase = TViewerPipeClient; +class TJsonNetInfo : public TViewerPipeClient { + using TThis = TJsonNetInfo; + using TBase = TViewerPipeClient; IViewer* Viewer; std::unordered_map TenantByPath; std::unordered_map TenantBySubDomainKey; @@ -40,16 +28,12 @@ class TJsonNetInfo : public TViewerPipeClient { TString Path; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonNetInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) : Viewer(viewer) , Event(ev) {} - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); @@ -195,7 +179,7 @@ class TJsonNetInfo : public TViewerPipeClient { } } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { THashMap nodeInfoIndex; if (NodesInfo) { for (const TEvInterconnect::TNodeInfo& nodeInfo : NodesInfo->Nodes) { @@ -297,61 +281,42 @@ class TJsonNetInfo : public TViewerPipeClient { Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: false - type: string - - name: hive_id - in: query - description: hive identifier (tablet id) - required: false - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Network information", + .Description = "Returns network information", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "hive_id", + .Description = "hive identifier (tablet id)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Network information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns network information"; - } -}; - -} } diff --git a/ydb/core/viewer/json_nodeinfo.h b/ydb/core/viewer/viewer_nodeinfo.h similarity index 70% rename from ydb/core/viewer/json_nodeinfo.h rename to ydb/core/viewer/viewer_nodeinfo.h index ac1dbb04878d..5165a1c96442 100644 --- a/ydb/core/viewer/json_nodeinfo.h +++ b/ydb/core/viewer/viewer_nodeinfo.h @@ -1,14 +1,7 @@ #pragma once -#include -#include -#include -#include -#include -#include "wb_merge.h" #include "json_wb_req.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { template <> struct TWhiteboardInfo { @@ -44,19 +37,4 @@ struct TWhiteboardInfo { using TJsonNodeInfo = TJsonWhiteboardRequest; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Interconnect information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about node connections"; - } -}; - -} } diff --git a/ydb/core/viewer/json_nodelist.h b/ydb/core/viewer/viewer_nodelist.h similarity index 70% rename from ydb/core/viewer/json_nodelist.h rename to ydb/core/viewer/viewer_nodelist.h index d1d3310f616e..d3b2963a1f4a 100644 --- a/ydb/core/viewer/json_nodelist.h +++ b/ydb/core/viewer/viewer_nodelist.h @@ -1,14 +1,11 @@ #pragma once +#include "json_handlers.h" +#include "viewer.h" +#include #include -#include -#include #include -#include -#include -#include "viewer.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -81,43 +78,37 @@ class TJsonNodeList : public TActorBootstrapped { void Timeout(const TActorContext &ctx) { ReplyAndDie(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return YAML::Load(R"___( - type: array - title: TEvNodeListResponse - items: - type: object - title: TNodeInfo - properties: - Id: - type: integer - Host: - type: string - Address: - type: string - Port: - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Nodes list"; - } -}; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns list of nodes"; + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - viewer + summary: Nodes list + description: Returns list of nodes + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: array + title: TEvNodeListResponse + items: + type: object + title: TNodeInfo + properties: + Id: + type: integer + Host: + type: string + Address: + type: string + Port: + type: integer + )___"); + return node; } }; } -} diff --git a/ydb/core/viewer/viewer_nodes.h b/ydb/core/viewer/viewer_nodes.h new file mode 100644 index 000000000000..ee7f42ceaee3 --- /dev/null +++ b/ydb/core/viewer/viewer_nodes.h @@ -0,0 +1,2574 @@ +#pragma once +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" +#include "viewer.h" +#include "viewer_helper.h" +#include "viewer_tabletinfo.h" +#include "wb_group.h" +#include + +namespace NKikimr::NViewer { + +using namespace NProtobufJson; +using namespace NActors; +using namespace NNodeWhiteboard; + +enum class ENodeFields : ui8 { + NodeInfo, + SystemState, + PDisks, + VDisks, + Tablets, + NodeId, + HostName, + NodeName, + DC, + Rack, + Version, + Uptime, + Memory, + CPU, + LoadAverage, + Missing, + DiskSpaceUsage, + SubDomainKey, + DisconnectTime, + Database, + HasDisks, + COUNT +}; + +constexpr ui8 operator +(ENodeFields e) { + return static_cast(e); +} + +class TJsonNodes : public TViewerPipeClient { + using TThis = TJsonNodes; + using TBase = TViewerPipeClient; + using TNodeId = ui32; + using TPDiskId = std::pair; + using TFieldsType = std::bitset<+ENodeFields::COUNT>; + + enum ENavigateRequest { + ENavigateRequestDatabase, + ENavigateRequestResource, + ENavigateRequestPath, + }; + + enum EBoardInfoRequest { + EBoardInfoRequestDatabase, + EBoardInfoRequestResource, + }; + + std::optional> NodesInfoResponse; + std::optional> NodeStateResponse; + std::optional> DatabaseBoardInfoResponse; + std::optional> ResourceBoardInfoResponse; + std::optional> PathNavigateResponse; + std::unordered_map> HiveNodeStats; + + std::vector HivesToAsk; + bool AskHiveAboutPaths = false; + + std::optional> StoragePoolsResponse; + std::optional> GroupsResponse; + std::optional> VSlotsResponse; + std::optional> PDisksResponse; + + int WhiteboardStateRequestsInFlight = 0; + std::unordered_map> SystemStateResponse; + std::unordered_map> VDiskStateResponse; + std::unordered_map> PDiskStateResponse; + std::unordered_map> TabletStateResponse; + std::unordered_map> SystemViewerResponse; + std::unordered_map> TabletViewerResponse; + + TJsonSettings JsonSettings; + ui32 Timeout = 0; + + enum ETimeoutTag : ui64 { + NoTimeout, + TimeoutTablets, + TimeoutFinal, + }; + + ETimeoutTag CurrentTimeoutState = NoTimeout; + + TString SharedDatabase; + bool FilterDatabase = false; + bool HasDatabaseNodes = false; + TPathId FilterPathId; + TSubDomainKey SubDomainKey; + TSubDomainKey SharedSubDomainKey; + bool FilterSubDomainKey = false; + TString FilterPath; + TString FilterStoragePool; + std::pair FilterStoragePoolId; + std::unordered_set FilterNodeIds; + std::unordered_set FilterGroupIds; + std::optional Offset; + std::optional Limit; + int UptimeSeconds = 0; + bool ProblemNodesOnly = false; + TString Filter; + bool AllWhiteboardFields = false; + + enum class EWith { + Everything, + MissingDisks, + SpaceProblems, + }; + EWith With = EWith::Everything; + + enum class EType { + Any, + Static, + Dynamic, + Storage, + }; + EType Type = EType::Any; + + enum class EFilterStorageStage { + None, + Pools, + Groups, + VSlots, + }; + + EFilterStorageStage FilterStorageStage = EFilterStorageStage::None; + TNodeId MinAllowedNodeId = std::numeric_limits::min(); + TNodeId MaxAllowedNodeId = std::numeric_limits::max(); + std::optional MaximumDisksPerNode; + std::optional MaximumSlotsPerDisk; + ui32 SpaceUsageProblem = 90; // % + bool OffloadMerge = true; + size_t OffloadMergeAttempts = 2; + + using TGroupSortKey = std::variant; + + struct TNode { + TEvInterconnect::TNodeInfo NodeInfo; + NKikimrWhiteboard::TSystemStateInfo SystemState; + std::vector PDisks; + std::vector SysViewPDisks; + std::vector VDisks; + std::vector SysViewVDisks; + std::vector Tablets; + TSubDomainKey SubDomainKey; + TString Database; + ui32 MissingDisks = 0; + float DiskSpaceUsage = 0; // the highest + float CpuUsage = 0; // total, normalized + float LoadAverage = 0; // normalized + bool Problems = false; + bool Connected = false; + bool Disconnected = false; + bool HasDisks = false; + bool GotDatabaseFromDatabaseBoardInfo = false; + bool GotDatabaseFromResourceBoardInfo = false; + int UptimeSeconds = 0; + + TNodeId GetNodeId() const { + return NodeInfo.NodeId; + } + + TString GetHostName() const { + if (NodeInfo.Host) { + return NodeInfo.Host; + } + if (SystemState.GetHost()) { + return SystemState.GetHost(); + } + if (NodeInfo.ResolveHost) { + return NodeInfo.ResolveHost; + } + return {}; + } + + TString GetNodeName() const { + return SystemState.GetNodeName(); + } + + TString GetDataCenter() const { + if (NodeInfo.Location.GetDataCenterId()) { + return NodeInfo.Location.GetDataCenterId(); + } + return SystemState.GetLocation().GetDataCenter(); + } + + TString GetRack() const { + if (NodeInfo.Location.GetRackId()) { + return NodeInfo.Location.GetRackId(); + } + return SystemState.GetLocation().GetRack(); + } + + void Cleanup() { + if (SystemState.HasSystemLocation()) { + SystemState.ClearSystemLocation(); + } + if (SystemState.HasLocation()) { + if (SystemState.GetLocation().GetDataCenter().empty()) { + SystemState.MutableLocation()->ClearDataCenter(); + } + if (SystemState.GetLocation().GetRack().empty()) { + SystemState.MutableLocation()->ClearRack(); + } + if (SystemState.GetLocation().GetUnit().empty() || SystemState.GetLocation().GetUnit() == "0") { + SystemState.MutableLocation()->ClearUnit(); + } + } + } + + void CalcDatabase() { + if (SystemState.TenantsSize() == 1) { + Database = SystemState.GetTenants(0); + } + } + + void CalcDisks() { + MissingDisks = 0; + DiskSpaceUsage = 0; + if (!PDisks.empty()) { + for (const auto& pdisk : PDisks) { + float diskSpaceUsage = pdisk.GetTotalSize() ? 100.0 * (pdisk.GetTotalSize() - pdisk.GetAvailableSize()) / pdisk.GetTotalSize() : 0; + DiskSpaceUsage = std::max(DiskSpaceUsage, diskSpaceUsage); + if (pdisk.state() == NKikimrBlobStorage::TPDiskState::Normal) { + continue; + } + ++MissingDisks; + } + } else { + for (const auto& entry : SysViewPDisks) { + const auto& pdisk(entry.GetInfo()); + float diskSpaceUsage = pdisk.GetTotalSize() ? 100.0 * (pdisk.GetTotalSize() - pdisk.GetAvailableSize()) / pdisk.GetTotalSize() : 0; + DiskSpaceUsage = std::max(DiskSpaceUsage, diskSpaceUsage); + NKikimrBlobStorage::EDriveStatus driveStatus = NKikimrBlobStorage::EDriveStatus::UNKNOWN; + if (NKikimrBlobStorage::EDriveStatus_Parse(pdisk.GetStatusV2(), &driveStatus)) { + switch (driveStatus) { + case NKikimrBlobStorage::EDriveStatus::ACTIVE: + case NKikimrBlobStorage::EDriveStatus::INACTIVE: + continue; + default: + ++MissingDisks; + break; + } + } + } + } + } + + void CalcCpuUsage() { + float usage = 0; + int threads = 0; + for (const auto& pool : SystemState.GetPoolStats()) { + usage += pool.GetUsage() * pool.GetThreads(); + threads += pool.GetThreads(); + } + CpuUsage = usage / threads; + } + + void CalcLoadAverage() { + if (SystemState.GetNumberOfCpus() && SystemState.LoadAverageSize() > 0) { + LoadAverage = SystemState.GetLoadAverage(0) / SystemState.GetNumberOfCpus(); + } + } + + void DisconnectNode() { + Problems = true; + Disconnected = true; + if (!SystemState.HasDisconnectTime()) { + TInstant disconnectTime; + for (const auto& entry : SysViewPDisks) { + const auto& pdisk(entry.GetInfo()); + disconnectTime = std::max(disconnectTime, TInstant::MicroSeconds(pdisk.GetStatusChangeTimestamp())); + } + if (disconnectTime) { + SystemState.SetDisconnectTime(disconnectTime.MilliSeconds()); + } + } + } + + void RemapDisks() { + if (PDisks.empty() && !SysViewPDisks.empty()) { + for (const auto& entry : SysViewPDisks) { + const auto& pdisk(entry.GetInfo()); + auto& pDiskState = PDisks.emplace_back(); + NKikimrBlobStorage::EDriveStatus driveStatus = NKikimrBlobStorage::EDriveStatus::UNKNOWN; + if (NKikimrBlobStorage::EDriveStatus_Parse(pdisk.GetStatusV2(), &driveStatus)) { + switch (driveStatus) { + case NKikimrBlobStorage::EDriveStatus::ACTIVE: + case NKikimrBlobStorage::EDriveStatus::INACTIVE: + pDiskState.SetState(NKikimrBlobStorage::TPDiskState::Normal); + break; + default: + break; + } + } + + pDiskState.SetPDiskId(entry.GetKey().GetPDiskId()); + pDiskState.SetNodeId(entry.GetKey().GetNodeId()); + pDiskState.SetPath(pdisk.GetPath()); + pDiskState.SetGuid(pdisk.GetGuid()); + pDiskState.SetTotalSize(pdisk.GetTotalSize()); + pDiskState.SetAvailableSize(pdisk.GetAvailableSize()); + pDiskState.SetExpectedSlotCount(pdisk.GetExpectedSlotCount()); + } + } + if (VDisks.empty() && !SysViewVDisks.empty()) { + for (const auto& entry : SysViewVDisks) { + const auto& vdisk(entry.GetInfo()); + auto& vDiskState = VDisks.emplace_back(); + vDiskState.MutableVDiskId()->SetGroupID(vdisk.GetGroupId()); + vDiskState.MutableVDiskId()->SetGroupGeneration(vdisk.GetGroupGeneration()); + vDiskState.MutableVDiskId()->SetRing(vdisk.GetFailRealm()); + vDiskState.MutableVDiskId()->SetDomain(vdisk.GetFailDomain()); + vDiskState.MutableVDiskId()->SetVDisk(vdisk.GetVDisk()); + vDiskState.SetNodeId(entry.GetKey().GetNodeId()); + vDiskState.SetPDiskId(entry.GetKey().GetPDiskId()); + vDiskState.SetAllocatedSize(vdisk.GetAllocatedSize()); + vDiskState.SetAvailableSize(vdisk.GetAvailableSize()); + vDiskState.SetVDiskSlotId(entry.GetKey().GetVSlotId()); + NKikimrBlobStorage::EVDiskStatus vDiskStatus; + if (NKikimrBlobStorage::EVDiskStatus_Parse(vdisk.GetStatusV2(), &vDiskStatus)) { + switch(vDiskStatus) { + case NKikimrBlobStorage::EVDiskStatus::ERROR: + vDiskState.SetVDiskState(NKikimrWhiteboard::EVDiskState::LocalRecoveryError); + break; + case NKikimrBlobStorage::EVDiskStatus::INIT_PENDING: + vDiskState.SetVDiskState(NKikimrWhiteboard::EVDiskState::Initial); + break; + case NKikimrBlobStorage::EVDiskStatus::REPLICATING: + vDiskState.SetVDiskState(NKikimrWhiteboard::EVDiskState::OK); + vDiskState.SetReplicated(false); + break; + case NKikimrBlobStorage::EVDiskStatus::READY: + vDiskState.SetVDiskState(NKikimrWhiteboard::EVDiskState::OK); + break; + } + } + } + } + } + + bool IsStatic() const { + return NodeInfo.IsStatic; + } + + NKikimrWhiteboard::EFlag GetOverall() const { + return SystemState.GetSystemState(); + } + + int GetCandidateScore() const { + int score = 0; + if (Connected) { + score += 100; + } + if (IsStatic()) { + score += 10; + } + return score; + } + + TString GetDiskUsageForGroup() const { + //return TStringBuilder() << std::ceil(std::clamp(DiskSpaceUsage, 0, 100) / 5) * 5 << '%'; + // we want 0%-95% groups instead of 5%-100% groups + return TStringBuilder() << std::floor(std::clamp(DiskSpaceUsage, 0, 100) / 5) * 5 << '%'; + } + + TInstant GetStartTime() const { + return TInstant::MilliSeconds(SystemState.GetStartTime()); + } + + TInstant GetDisconnectTime() const { + return TInstant::MilliSeconds(SystemState.GetDisconnectTime()); + } + + int GetUptimeSeconds(TInstant now) const { + if (Disconnected) { + return static_cast(GetDisconnectTime().Seconds()) - static_cast(now.Seconds()); // negative for disconnected nodes + } else { + return static_cast(now.Seconds()) - static_cast(GetStartTime().Seconds()); + } + } + + void CalcUptimeSeconds(TInstant now) { + UptimeSeconds = GetUptimeSeconds(now); + } + + TString GetUptimeForGroup() const { + if (!Disconnected && UptimeSeconds >= 0) { + if (UptimeSeconds < 60 * 10) { + return "up <10m"; + } + if (UptimeSeconds < 60 * 60) { + return "up <1h"; + } + if (UptimeSeconds < 60 * 60 * 24) { + return "up <24h"; + } + if (UptimeSeconds < 60 * 60 * 24 * 7) { + return "up 24h+"; + } + return "up 1 week+"; + } else { + if (SystemState.HasDisconnectTime()) { + if (UptimeSeconds > -60 * 10) { + return "down <10m"; + } + if (UptimeSeconds > -60 * 60) { + return "down <1h"; + } + if (UptimeSeconds > -60 * 60 * 24) { + return "down <24h"; + } + if (UptimeSeconds > -60 * 60 * 24 * 7) { + return "down 24h+"; + } + return "down 1 week+"; + } else { + return "disconnected"; + } + } + } + + TString GetVersionForGroup() const { + if (SystemState.HasVersion()) { + return SystemState.GetVersion(); + } else { + return "unknown"; + } + } + + bool HasDatabase(const TString& database) const { + return Database == database; + } + + bool HasSubDomainKey(const TSubDomainKey& subDomainKey) const { + return SubDomainKey == subDomainKey; + } + + TString GetGroupName(ENodeFields groupBy) const { + TString groupName; + switch (groupBy) { + case ENodeFields::NodeId: + groupName = ToString(GetNodeId()); + break; + case ENodeFields::HostName: + groupName = GetHostName(); + break; + case ENodeFields::NodeName: + groupName = GetNodeName(); + break; + case ENodeFields::Database: + groupName = Database; + break; + case ENodeFields::DiskSpaceUsage: + groupName = GetDiskUsageForGroup(); + break; + case ENodeFields::DC: + groupName = GetDataCenter(); + break; + case ENodeFields::Rack: + groupName = GetRack(); + break; + case ENodeFields::Missing: + groupName = ToString(MissingDisks); + break; + case ENodeFields::Uptime: + groupName = GetUptimeForGroup(); + break; + case ENodeFields::Version: + groupName = GetVersionForGroup(); + break; + default: + break; + } + if (groupName.empty()) { + groupName = "unknown"; + } + return groupName; + } + + TGroupSortKey GetGroupSortKey(ENodeFields groupBy) const { + switch (groupBy) { + case ENodeFields::NodeId: + case ENodeFields::HostName: + case ENodeFields::NodeName: + case ENodeFields::Database: + case ENodeFields::DC: + case ENodeFields::Rack: + case ENodeFields::Version: + return GetGroupName(groupBy); + case ENodeFields::DiskSpaceUsage: + return DiskSpaceUsage; + case ENodeFields::Missing: + return MissingDisks; + case ENodeFields::Uptime: + return UptimeSeconds; + default: + return TString(); + } + } + + void MergeFrom(const NKikimrWhiteboard::TSystemStateInfo& systemState, TInstant now) { + SystemState.MergeFrom(systemState); + Cleanup(); + CalcDatabase(); + CalcCpuUsage(); + CalcLoadAverage(); + CalcUptimeSeconds(now); + } + }; + + struct TNodeBatch { + std::vector NodesToAskFor; + std::vector NodesToAskAbout; + size_t Offset = 0; + bool HasStaticNodes = false; + + TNodeId ChooseNodeId() { + if (Offset >= NodesToAskFor.size()) { + return 0; + } + return NodesToAskFor[Offset++]->GetNodeId(); + } + }; + + using TNodeData = std::vector; + using TNodeView = std::deque; + + struct TNodeGroup { + TString Name; + TGroupSortKey SortKey; + TNodeView Nodes; + }; + + TNodeData NodeData; + TNodeView NodeView; + std::vector NodeGroups; + std::unordered_map NodesByNodeId; + std::unordered_map NodeBatches; + + TFieldsType FieldsRequired; + TFieldsType FieldsAvailable; + const TFieldsType FieldsAll = TFieldsType().set(); + const TFieldsType FieldsNodeInfo = TFieldsType().set(+ENodeFields::NodeInfo) + .set(+ENodeFields::NodeId) + .set(+ENodeFields::HostName) + .set(+ENodeFields::DC) + .set(+ENodeFields::Rack); + const TFieldsType FieldsSystemState = TFieldsType().set(+ENodeFields::SystemState) + .set(+ENodeFields::Database) + .set(+ENodeFields::NodeName) + .set(+ENodeFields::Version) + .set(+ENodeFields::Uptime) + .set(+ENodeFields::Memory) + .set(+ENodeFields::CPU) + .set(+ENodeFields::LoadAverage); + const TFieldsType FieldsPDisks = TFieldsType().set(+ENodeFields::PDisks) + .set(+ENodeFields::Missing) + .set(+ENodeFields::DiskSpaceUsage); + const TFieldsType FieldsVDisks = TFieldsType().set(+ENodeFields::VDisks); + const TFieldsType FieldsTablets = TFieldsType().set(+ENodeFields::Tablets); + const TFieldsType FieldsHiveNodeStat = TFieldsType().set(+ENodeFields::SubDomainKey) + .set(+ENodeFields::DisconnectTime); + + const std::unordered_map DependentFields = { + { ENodeFields::DC, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Rack, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Uptime, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Version, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::NodeName, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::CPU, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Memory, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::LoadAverage, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Database, TFieldsType().set(+ENodeFields::SystemState) }, + { ENodeFields::Missing, TFieldsType().set(+ENodeFields::PDisks) }, + }; + + bool FieldsNeeded(TFieldsType fields) const { + return (FieldsRequired & (fields & ~FieldsAvailable)).any(); + } + + ENodeFields SortBy = ENodeFields::NodeId; + bool ReverseSort = false; + ENodeFields GroupBy = ENodeFields::NodeId; + ENodeFields FilterGroupBy = ENodeFields::NodeId; + TString FilterGroup; + bool NeedFilter = false; + bool NeedGroup = false; + bool NeedSort = false; + bool NeedLimit = false; + ui64 TotalNodes = 0; + ui64 FoundNodes = 0; + bool NoRack = false; + bool NoDC = false; + std::vector Problems; + + void AddProblem(const TString& problem) { + for (const auto& p : Problems) { + if (p == problem) { + return; + } + } + Problems.push_back(problem); + } + + static ENodeFields ParseENodeFields(TStringBuf field) { + ENodeFields result = ENodeFields::COUNT; + if (field == "NodeId" || field == "Id") { + result = ENodeFields::NodeId; + } else if (field == "Host") { + result = ENodeFields::HostName; + } else if (field == "NodeName") { + result = ENodeFields::NodeName; + } else if (field == "DC") { + result = ENodeFields::DC; + } else if (field == "Rack") { + result = ENodeFields::Rack; + } else if (field == "Version") { + result = ENodeFields::Version; + } else if (field == "Uptime") { + result = ENodeFields::Uptime; + } else if (field == "Memory") { + result = ENodeFields::Memory; + } else if (field == "CPU") { + result = ENodeFields::CPU; + } else if (field == "LoadAverage") { + result = ENodeFields::LoadAverage; + } else if (field == "Missing") { + result = ENodeFields::Missing; + } else if (field == "DiskSpaceUsage") { + result = ENodeFields::DiskSpaceUsage; + } else if (field == "DisconnectTime") { + result = ENodeFields::DisconnectTime; + } else if (field == "Database") { + result = ENodeFields::Database; + } else if (field == "SubDomainKey") { + result = ENodeFields::SubDomainKey; + } else if (field == "SystemState") { + result = ENodeFields::SystemState; + } else if (field == "PDisks") { + result = ENodeFields::PDisks; + } else if (field == "VDisks") { + result = ENodeFields::VDisks; + } else if (field == "Tablets") { + result = ENodeFields::Tablets; + } + return result; + } + +public: + TJsonNodes(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev, "/viewer/nodes") + { + const auto& params(Event->Get()->Request.GetParams()); + JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); + JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); + InitConfig(params); + Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + FieldsRequired.set(+ENodeFields::NodeId); + UptimeSeconds = FromStringWithDefault(params.Get("uptime"), 0); + ProblemNodesOnly = FromStringWithDefault(params.Get("problems_only"), ProblemNodesOnly); + Filter = params.Get("filter"); + if (UptimeSeconds || ProblemNodesOnly || !Filter.empty()) { + FieldsRequired.set(+ENodeFields::SystemState); + } + FilterPath = params.Get("path"); + if (FilterPath && !Database) { + Database = FilterPath; + } + if (Database) { + FilterDatabase = true; + } + if (FilterPath == Database) { + FilterPath.clear(); + } + if (params.Has("filter_group") && params.Has("filter_group_by")) { + FilterGroup = params.Get("filter_group"); + FilterGroupBy = ParseENodeFields(params.Get("filter_group_by")); + FieldsRequired.set(+FilterGroupBy); + } + + OffloadMerge = FromStringWithDefault(params.Get("offload_merge"), OffloadMerge); + OffloadMergeAttempts = FromStringWithDefault(params.Get("offload_merge_attempts"), OffloadMergeAttempts); + Direct = FromStringWithDefault(params.Get("direct"), Direct); + FilterStoragePool = params.Get("pool"); + if (FilterStoragePool.empty()) { + FilterStoragePool = params.Get("storage_pool"); + } + if (params.Has("group_id")) { + FilterGroupIds.insert(FromStringWithDefault(params.Get("group_id"), -1)); + } + SplitIds(params.Get("node_id"), ',', FilterNodeIds); + auto itZero = FilterNodeIds.find(0); + if (itZero != FilterNodeIds.end()) { + FilterNodeIds.erase(itZero); + FilterNodeIds.insert(TlsActivationContext->ActorSystem()->NodeId); + } + if (params.Get("with") == "missing") { + With = EWith::MissingDisks; + FieldsRequired.set(+ENodeFields::Missing); + } else if (params.Get("with") == "space") { + With = EWith::SpaceProblems; + FieldsRequired.set(+ENodeFields::DiskSpaceUsage); + } + if (params.Get("type") == "static") { + Type = EType::Static; + FieldsRequired.set(+ENodeFields::NodeInfo); + } else if (params.Get("type") == "dynamic") { + Type = EType::Dynamic; + FieldsRequired.set(+ENodeFields::NodeInfo); + } else if (params.Get("type") == "storage") { + Type = EType::Storage; + FieldsRequired.set(+ENodeFields::NodeInfo); + } else if (params.Get("type") == "any") { + Type = EType::Any; + } + NeedFilter = (With != EWith::Everything) || (Type != EType::Any) || !Filter.empty() || !FilterNodeIds.empty() || ProblemNodesOnly || UptimeSeconds > 0 || !FilterGroup.empty(); + if (params.Has("offset")) { + Offset = FromStringWithDefault(params.Get("offset"), 0); + NeedLimit = true; + } + if (params.Has("limit")) { + Limit = FromStringWithDefault(params.Get("limit"), std::numeric_limits::max()); + NeedLimit = true; + } + if (FromStringWithDefault(params.Get("storage"))) { + FieldsRequired.set(+ENodeFields::PDisks); + FieldsRequired.set(+ENodeFields::VDisks); + } + if (FromStringWithDefault(params.Get("tablets"))) { + FieldsRequired.set(+ENodeFields::Tablets); + } + TStringBuf sort = params.Get("sort"); + if (sort) { + NeedSort = true; + if (sort.StartsWith("-") || sort.StartsWith("+")) { + ReverseSort = (sort[0] == '-'); + sort.Skip(1); + } + SortBy = ParseENodeFields(sort); + FieldsRequired.set(+SortBy); + } + TString fieldsRequired = params.Get("fields_required"); + if (!fieldsRequired.empty()) { + if (fieldsRequired == "all") { + FieldsRequired = FieldsAll; + } else { + TStringBuf source = fieldsRequired; + for (TStringBuf value = source.NextTok(','); !value.empty(); value = source.NextTok(',')) { + ENodeFields field = ParseENodeFields(value); + if (field != ENodeFields::COUNT) { + FieldsRequired.set(+field); + } + } + } + } else { + FieldsRequired.set(+ENodeFields::SystemState); + } + TStringBuf group = params.Get("group"); + if (group) { + NeedGroup = true; + GroupBy = ParseENodeFields(group); + FieldsRequired.set(+GroupBy); + NeedSort = false; + NeedLimit = false; + } + for (auto field = +ENodeFields::NodeId; field != +ENodeFields::COUNT; ++field) { + if (FieldsRequired.test(field)) { + auto itDependentFields = DependentFields.find(static_cast(field)); + if (itDependentFields != DependentFields.end()) { + FieldsRequired |= itDependentFields->second; + } + } + } + if (FromStringWithDefault(params.Get("all_whiteboard_fields"), false)) { + AllWhiteboardFields = true; + } + } + + void Bootstrap() override { + if (TBase::NeedToRedirect()) { + return; + } + + if (FieldsNeeded(FieldsNodeInfo)) { + NodesInfoResponse = MakeRequest(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); + NodeStateResponse = MakeWhiteboardRequest(TActivationContext::ActorSystem()->NodeId, new TEvWhiteboard::TEvNodeStateRequest()); + } + if (FilterStoragePool || !FilterGroupIds.empty()) { + FilterDatabase = false; // we disable database filter if we're filtering by pool or group + } + if (FilterDatabase) { + if (!DatabaseNavigateResponse) { + DatabaseNavigateResponse = MakeRequestSchemeCacheNavigate(Database, ENavigateRequestDatabase); + } + if (!FieldsNeeded(FieldsHiveNodeStat) && !(FilterPath && FieldsNeeded(FieldsTablets))) { + DatabaseBoardInfoResponse = MakeRequestStateStorageEndpointsLookup(Database, EBoardInfoRequestDatabase); + } + } + if (FilterPath && FieldsNeeded(FieldsTablets)) { + PathNavigateResponse = MakeRequestSchemeCacheNavigate(FilterPath, ENavigateRequestPath); + } + if (FilterStoragePool) { + StoragePoolsResponse = RequestBSControllerPools(); + GroupsResponse = RequestBSControllerGroups(); + VSlotsResponse = RequestBSControllerVSlots(); + FilterStorageStage = EFilterStorageStage::Pools; + } else if (!FilterGroupIds.empty()) { + VSlotsResponse = RequestBSControllerVSlots(); + FilterStorageStage = EFilterStorageStage::VSlots; + } + if (With != EWith::Everything) { + PDisksResponse = RequestBSControllerPDisks(); + } + if (ProblemNodesOnly || GroupBy == ENodeFields::Uptime) { + FieldsRequired.set(+ENodeFields::SystemState); + TTabletId rootHiveId = AppData()->DomainsInfo->GetHive(); + HivesToAsk.push_back(rootHiveId); + if (!PDisksResponse) { + PDisksResponse = RequestBSControllerPDisks(); + } + } + if (FieldsRequired.test(+ENodeFields::PDisks)) { + if (!PDisksResponse) { + PDisksResponse = RequestBSControllerPDisks(); + } + } + if (FieldsRequired.test(+ENodeFields::VDisks)) { + if (!VSlotsResponse) { + VSlotsResponse = RequestBSControllerVSlots(); + } + } + if (FieldsNeeded(FieldsHiveNodeStat) && !FilterDatabase && !FilterPath) { + TTabletId rootHiveId = AppData()->DomainsInfo->GetHive(); + HivesToAsk.push_back(rootHiveId); + } + Schedule(TDuration::MilliSeconds(Timeout * 50 / 100), new TEvents::TEvWakeup(TimeoutTablets)); // 50% timeout (for tablets) + TBase::Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup(TimeoutFinal)); + } + + void InvalidateNodes() { + NodesByNodeId.clear(); + } + + void RebuildNodesByNodeId() { + NodesByNodeId.clear(); + for (TNode* node : NodeView) { + NodesByNodeId.emplace(node->GetNodeId(), node); + } + } + + TNode* FindNode(TNodeId nodeId) { + if (NodesByNodeId.empty()) { + RebuildNodesByNodeId(); + } + auto itNode = NodesByNodeId.find(nodeId); + if (itNode != NodesByNodeId.end()) { + return itNode->second; + } + return nullptr; + } + + bool PreFilterDone() const { + return !FilterDatabase && FilterStorageStage == EFilterStorageStage::None; + } + + bool FilterDone() const { + return PreFilterDone() && !NeedFilter; + } + + void ApplyFilter() { + // database pre-filter, affects TotalNodes count + if (FilterDatabase) { + if (FilterSubDomainKey && FieldsAvailable.test(+ENodeFields::SubDomainKey)) { + TNodeView nodeView; + if (HasDatabaseNodes) { + for (TNode* node : NodeView) { + if (node->HasSubDomainKey(SubDomainKey)) { + nodeView.push_back(node); + } + } + } else { + for (TNode* node : NodeView) { + if (node->HasSubDomainKey(SharedSubDomainKey)) { + nodeView.push_back(node); + } + } + } + NodeView.swap(nodeView); + FoundNodes = TotalNodes = NodeView.size(); + InvalidateNodes(); + FilterDatabase = false; + } else if (FieldsAvailable.test(+ENodeFields::Database)) { + TNodeView nodeView; + if (HasDatabaseNodes) { + for (TNode* node : NodeView) { + if (node->HasDatabase(Database)) { + nodeView.push_back(node); + } + } + } else { + for (TNode* node : NodeView) { + if (node->HasDatabase(SharedDatabase)) { + nodeView.push_back(node); + } + } + } + NodeView.swap(nodeView); + FoundNodes = TotalNodes = NodeView.size(); + InvalidateNodes(); + FilterDatabase = false; + } else { + return; + } + } + // storage/nodes pre-filter, affects TotalNodes count + if (FilterStorageStage != EFilterStorageStage::None) { + return; + } + if (((Type == EType::Static || Type == EType::Dynamic) && FieldsAvailable.test(+ENodeFields::NodeInfo)) || (Type == EType::Storage && FieldsAvailable.test(+ENodeFields::HasDisks))) { + TNodeView nodeView; + switch (Type) { + case EType::Static: + for (TNode* node : NodeView) { + if (node->IsStatic()) { + nodeView.push_back(node); + } + } + break; + case EType::Dynamic: + for (TNode* node : NodeView) { + if (!node->IsStatic()) { + nodeView.push_back(node); + } + } + break; + case EType::Storage: + for (TNode* node : NodeView) { + if (node->HasDisks) { + nodeView.push_back(node); + } + } + break; + case EType::Any: + break; + } + NodeView.swap(nodeView); + FoundNodes = TotalNodes = NodeView.size(); + Type = EType::Any; + InvalidateNodes(); + } + // storage/nodes pre-filter, affects TotalNodes count + if (Type != EType::Any) { + return; + } + if (!FilterNodeIds.empty() && FieldsAvailable.test(+ENodeFields::NodeId)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (FilterNodeIds.count(node->GetNodeId()) > 0) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + FoundNodes = TotalNodes = NodeView.size(); + InvalidateNodes(); + FilterNodeIds.clear(); + } + if (NeedFilter) { + if (With == EWith::MissingDisks && FieldsAvailable.test(+ENodeFields::Missing)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (node->MissingDisks != 0) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + With = EWith::Everything; + InvalidateNodes(); + } + if (With == EWith::SpaceProblems && FieldsAvailable.test(+ENodeFields::DiskSpaceUsage)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (node->DiskSpaceUsage >= SpaceUsageProblem) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + With = EWith::Everything; + InvalidateNodes(); + } + if (ProblemNodesOnly && FieldsAvailable.test(+ENodeFields::SystemState)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (node->GetOverall() != NKikimrWhiteboard::EFlag::Green) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + ProblemNodesOnly = false; + InvalidateNodes(); + } + if (UptimeSeconds > 0 && FieldsAvailable.test(+ENodeFields::SystemState)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (node->UptimeSeconds < UptimeSeconds) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + UptimeSeconds = 0; + InvalidateNodes(); + } + if (!Filter.empty() && FieldsAvailable.test(+ENodeFields::NodeInfo)) { + TVector filterWords = SplitString(Filter, " "); + TNodeView nodeView; + for (TNode* node : NodeView) { + for (const TString& word : filterWords) { + if (node->GetHostName().Contains(word) || ::ToString(node->GetNodeId()).Contains(word)) { + nodeView.push_back(node); + } + } + } + NodeView.swap(nodeView); + Filter.clear(); + InvalidateNodes(); + } + if (!FilterGroup.empty() && FieldsAvailable.test(+FilterGroupBy)) { + TNodeView nodeView; + for (TNode* node : NodeView) { + if (node->GetGroupName(FilterGroupBy) == FilterGroup) { + nodeView.push_back(node); + } + } + NodeView.swap(nodeView); + FilterGroup.clear(); + InvalidateNodes(); + } + NeedFilter = (With != EWith::Everything) || (Type != EType::Any) || !Filter.empty() || !FilterNodeIds.empty() || ProblemNodesOnly || UptimeSeconds > 0 || !FilterGroup.empty(); + FoundNodes = NodeView.size(); + } + } + + void GroupCollection() { + std::unordered_map nodeGroups; + NodeGroups.clear(); + for (TNode* node : NodeView) { + auto gb = node->GetGroupName(GroupBy); + TNodeGroup* nodeGroup = nullptr; + auto it = nodeGroups.find(gb); + if (it == nodeGroups.end()) { + nodeGroups.emplace(gb, NodeGroups.size()); + nodeGroup = &NodeGroups.emplace_back(); + nodeGroup->Name = gb; + nodeGroup->SortKey = node->GetGroupSortKey(GroupBy); + } else { + nodeGroup = &NodeGroups[it->second]; + } + nodeGroup->Nodes.push_back(node); + } + } + + void ApplyGroup() { + if (FilterDone() && NeedGroup && FieldsAvailable.test(+GroupBy)) { + switch (GroupBy) { + case ENodeFields::NodeId: + case ENodeFields::HostName: + case ENodeFields::NodeName: + case ENodeFields::Database: + case ENodeFields::DC: + case ENodeFields::Rack: + case ENodeFields::Uptime: + GroupCollection(); + SortCollection(NodeGroups, [](const TNodeGroup& nodeGroup) { return nodeGroup.SortKey; }); + NeedGroup = false; + break; + case ENodeFields::DiskSpaceUsage: + case ENodeFields::Missing: + case ENodeFields::Version: + GroupCollection(); + SortCollection(NodeGroups, [](const TNodeGroup& nodeGroup) { return nodeGroup.SortKey; }, true); + NeedGroup = false; + break; + case ENodeFields::NodeInfo: + case ENodeFields::SystemState: + case ENodeFields::PDisks: + case ENodeFields::VDisks: + case ENodeFields::Tablets: + case ENodeFields::SubDomainKey: + case ENodeFields::COUNT: + case ENodeFields::Memory: + case ENodeFields::CPU: + case ENodeFields::LoadAverage: + case ENodeFields::DisconnectTime: + case ENodeFields::HasDisks: + break; + } + } + } + + void ApplySort() { + if (FilterDone() && NeedSort && FieldsAvailable.test(+SortBy)) { + switch (SortBy) { + case ENodeFields::NodeId: + SortCollection(NodeView, [](const TNode* node) { return node->GetNodeId(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::HostName: + SortCollection(NodeView, [](const TNode* node) { return node->GetHostName(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::NodeName: + SortCollection(NodeView, [](const TNode* node) { return node->GetNodeName(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::DC: + SortCollection(NodeView, [](const TNode* node) { return node->NodeInfo.Location.GetDataCenterId(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Rack: + SortCollection(NodeView, [](const TNode* node) { return node->NodeInfo.Location.GetRackId(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Version: + SortCollection(NodeView, [](const TNode* node) { return node->SystemState.GetVersion(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Uptime: + SortCollection(NodeView, [](const TNode* node) { return node->UptimeSeconds; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Memory: + SortCollection(NodeView, [](const TNode* node) { return node->SystemState.GetMemoryUsed(); }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::CPU: + SortCollection(NodeView, [](const TNode* node) { return node->CpuUsage; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::LoadAverage: + SortCollection(NodeView, [](const TNode* node) { return node->LoadAverage; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Missing: + SortCollection(NodeView, [](const TNode* node) { return node->MissingDisks; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::DiskSpaceUsage: + SortCollection(NodeView, [](const TNode* node) { return node->DiskSpaceUsage; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::Database: + SortCollection(NodeView, [](const TNode* node) { return node->Database; }, ReverseSort); + NeedSort = false; + break; + case ENodeFields::NodeInfo: + case ENodeFields::SystemState: + case ENodeFields::PDisks: + case ENodeFields::VDisks: + case ENodeFields::Tablets: + case ENodeFields::SubDomainKey: + case ENodeFields::DisconnectTime: + case ENodeFields::HasDisks: + case ENodeFields::COUNT: + break; + } + if (!NeedSort) { + InvalidateNodes(); + } + } + } + + void ApplyLimit() { + if (FilterDone() && !NeedSort && !NeedGroup && NeedLimit) { + if (Offset) { + NodeView.erase(NodeView.begin(), NodeView.begin() + std::min(*Offset, NodeView.size())); + InvalidateNodes(); + } + if (Limit) { + NodeView.resize(std::min(*Limit, NodeView.size())); + InvalidateNodes(); + } + NeedLimit = false; + } + } + + void ApplyEverything() { + ApplyFilter(); + ApplyGroup(); + ApplySort(); + ApplyLimit(); + } + + static constexpr size_t BATCH_SIZE = 200; + + void BuildCandidates(TNodeBatch& batch, std::vector& candidates) { + auto itCandidate = candidates.begin(); + for (; itCandidate != candidates.end() && batch.NodesToAskFor.size() < OffloadMergeAttempts; ++itCandidate) { + batch.NodesToAskFor.push_back(*itCandidate); + } + candidates.erase(candidates.begin(), itCandidate); + for (TNode* node : batch.NodesToAskAbout) { + if (node->IsStatic()) { + batch.HasStaticNodes = true; + } + } + } + + void SplitBatch(TNodeBatch& nodeBatch, std::vector& batches) { + std::vector candidates = nodeBatch.NodesToAskAbout; + std::sort(candidates.begin(), candidates.end(), [](TNode* a, TNode* b) { + return a->GetCandidateScore() > b->GetCandidateScore(); + }); + while (nodeBatch.NodesToAskAbout.size() > BATCH_SIZE) { + TNodeBatch newBatch; + size_t splitSize = std::min(BATCH_SIZE, nodeBatch.NodesToAskAbout.size() / 2); + newBatch.NodesToAskAbout.reserve(splitSize); + for (size_t i = 0; i < splitSize; ++i) { + newBatch.NodesToAskAbout.push_back(nodeBatch.NodesToAskAbout.back()); + nodeBatch.NodesToAskAbout.pop_back(); + } + BuildCandidates(newBatch, candidates); + batches.emplace_back(std::move(newBatch)); + } + if (!nodeBatch.NodesToAskAbout.empty()) { + BuildCandidates(nodeBatch, candidates); + batches.emplace_back(std::move(nodeBatch)); + } + } + + std::vector BatchNodes() { + std::vector batches; + if (OffloadMerge) { + std::unordered_map batchSubDomain; + std::unordered_map batchDataCenters; + for (TNode* node : NodeView) { + if (node->IsStatic()) { + batchDataCenters[node->GetDataCenter()].NodesToAskAbout.push_back(node); + } else { + batchSubDomain[node->SubDomainKey].NodesToAskAbout.push_back(node); + } + } + for (auto& [subDomainKey, nodeBatch] : batchSubDomain) { + if (nodeBatch.NodesToAskAbout.size() == 1) { + TNode* node = nodeBatch.NodesToAskAbout.front(); + batchDataCenters[node->GetDataCenter()].NodesToAskAbout.push_back(node); + } else { + SplitBatch(nodeBatch, batches); + } + } + for (auto& [dataCenter, nodeBatch] : batchDataCenters) { + SplitBatch(nodeBatch, batches); + } + } else { + TNodeBatch nodeBatch; + for (TNode* node : NodeView) { + nodeBatch.NodesToAskAbout.push_back(node); + } + SplitBatch(nodeBatch, batches); + } + return batches; + } + + bool HiveResponsesDone() const { + for (const auto& [hiveId, hiveNodeStats] : HiveNodeStats) { + if (!hiveNodeStats.IsDone()) { + return false; + } + } + return !HiveNodeStats.empty(); + } + + bool TimeToAskHive() { + if (NodesInfoResponse && !NodesInfoResponse->IsDone()) { + return false; + } + if (DatabaseNavigateResponse && !DatabaseNavigateResponse->IsDone()) { + return false; + } + if (ResourceNavigateResponse && !ResourceNavigateResponse->IsDone()) { + return false; + } + if (PathNavigateResponse && !PathNavigateResponse->IsDone()) { + return false; + } + return CurrentTimeoutState < TimeoutTablets; + } + + bool TimeToAskWhiteboard() { + if (NodesInfoResponse && !NodesInfoResponse->IsDone()) { + return false; + } + if (NodeStateResponse && !NodeStateResponse->IsDone()) { + return false; + } + if (DatabaseNavigateResponse && !DatabaseNavigateResponse->IsDone()) { + return false; + } + if (ResourceNavigateResponse && !ResourceNavigateResponse->IsDone()) { + return false; + } + if (PathNavigateResponse && !PathNavigateResponse->IsDone()) { + return false; + } + if (DatabaseBoardInfoResponse && !DatabaseBoardInfoResponse->IsDone()) { + return false; + } + if (ResourceBoardInfoResponse && !ResourceBoardInfoResponse->IsDone()) { + return false; + } + for (const auto& [hiveId, hiveNodeStats] : HiveNodeStats) { + if (!hiveNodeStats.IsDone()) { + AddEvent("HiveNodeStats not done"); + return false; + } + } + if (StoragePoolsResponse && !StoragePoolsResponse->IsDone()) { + return false; + } + if (GroupsResponse && !GroupsResponse->IsDone()) { + return false; + } + if (VSlotsResponse && !VSlotsResponse->IsDone()) { + return false; + } + if (PDisksResponse && !PDisksResponse->IsDone()) { + return false; + } + if (!SystemStateResponse.empty() || !TabletStateResponse.empty() || !PDiskStateResponse.empty() + || !VDiskStateResponse.empty() || !SystemViewerResponse.empty() || !TabletViewerResponse.empty()) { + return false; + } + return CurrentTimeoutState < TimeoutFinal; + } + + static TString GetDatabaseFromEndpointsBoardPath(const TString& path) { + TStringBuf db(path); + db.SkipPrefix("gpc+"); + return TString(db); + } + + void ProcessResponses() { + AddEvent("ProcessResponses"); + if (NodesInfoResponse && NodesInfoResponse->IsDone()) { + if (NodesInfoResponse->IsOk()) { + bool seenDC = false; + bool seenRack = false; + for (const auto& ni : NodesInfoResponse->Get()->Nodes) { + TNode& node = NodeData.emplace_back(); + node.NodeInfo = ni; + if (ni.Host && !node.SystemState.GetHost()) { + node.SystemState.SetHost(ni.Host); + } + if (ni.Location.GetDataCenterId() != 0) { + seenDC = true; + } + if (ni.Location.GetRackId() != 0) { + seenRack = true; + } + } + for (TNode& node : NodeData) { + NodeView.emplace_back(&node); + } + InvalidateNodes(); + FieldsAvailable |= FieldsNodeInfo; + FoundNodes = TotalNodes = NodeView.size(); + NoDC = !seenDC; + NoRack = !seenRack; + } else { + AddProblem("no-nodes-info"); + } + NodesInfoResponse.reset(); + } + + if (NodeStateResponse && NodeStateResponse->IsDone() && TotalNodes > 0) { + if (NodeStateResponse->IsOk()) { + for (const auto& nodeStateInfo : NodeStateResponse->Get()->Record.GetNodeStateInfo()) { + if (nodeStateInfo.GetConnected()) { + TNodeId nodeId = FromStringWithDefault(TStringBuf(nodeStateInfo.GetPeerName()).Before(':'), 0); + if (nodeId) { + TNode* node = FindNode(nodeId); + if (node) { + node->Connected = true; + } + } + } + } + } else { + AddProblem("no-node-state-info"); + } + NodeStateResponse.reset(); + } + + if (DatabaseNavigateResponse && DatabaseNavigateResponse->IsDone()) { // database hive and subdomain key + if (DatabaseNavigateResponse->IsOk()) { + auto* ev = DatabaseNavigateResponse->Get(); + if (ev->Request->ResultSet.size() == 1 && ev->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { + TSchemeCacheNavigate::TEntry& entry(ev->Request->ResultSet.front()); + if (entry.DomainInfo) { + if (entry.DomainInfo->ResourcesDomainKey && entry.DomainInfo->DomainKey != entry.DomainInfo->ResourcesDomainKey) { + TPathId resourceDomainKey(entry.DomainInfo->ResourcesDomainKey); + ResourceNavigateResponse = MakeRequestSchemeCacheNavigate(resourceDomainKey, ENavigateRequestResource); + } + if (FieldsNeeded(FieldsHiveNodeStat) || (FilterPath && FieldsNeeded(FieldsTablets))) { + const auto ownerId = entry.DomainInfo->DomainKey.OwnerId; + const auto localPathId = entry.DomainInfo->DomainKey.LocalPathId; + SubDomainKey = TSubDomainKey(ownerId, localPathId); + if (FilterDatabase) { + FilterSubDomainKey = true; + } + HivesToAsk.push_back(AppData()->DomainsInfo->GetHive()); + if (entry.DomainInfo->Params.HasHive()) { + HivesToAsk.push_back(entry.DomainInfo->Params.GetHive()); + } + } + } + } + } else { + NodeView.clear(); + AddProblem("no-database-info"); + } + DatabaseNavigateResponse.reset(); + } + + if (ResourceNavigateResponse && ResourceNavigateResponse->IsDone()) { // database hive and subdomain key + if (ResourceNavigateResponse->IsOk()) { + auto* ev = ResourceNavigateResponse->Get(); + if (ev->Request->ResultSet.size() == 1 && ev->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { + TSchemeCacheNavigate::TEntry& entry(ev->Request->ResultSet.front()); + auto path = CanonizePath(entry.Path); + SharedDatabase = path; + if (FieldsNeeded(FieldsHiveNodeStat) || (FilterPath && FieldsNeeded(FieldsTablets))) { + HivesToAsk.push_back(AppData()->DomainsInfo->GetHive()); + if (entry.DomainInfo) { + const auto ownerId = entry.DomainInfo->DomainKey.OwnerId; + const auto localPathId = entry.DomainInfo->DomainKey.LocalPathId; + SharedSubDomainKey = TSubDomainKey(ownerId, localPathId); + if (FilterDatabase) { + FilterSubDomainKey = true; + } + if (entry.DomainInfo->Params.HasHive()) { + HivesToAsk.push_back(entry.DomainInfo->Params.GetHive()); + } + } + } else { + ResourceBoardInfoResponse = MakeRequestStateStorageEndpointsLookup(path, EBoardInfoRequestResource); + } + } + } else { + NodeView.clear(); + AddProblem("no-shared-database-info"); + } + ResourceNavigateResponse.reset(); + } + + if (PathNavigateResponse && PathNavigateResponse->IsDone()) { // filter path id + if (PathNavigateResponse->IsOk()) { + auto* ev = PathNavigateResponse->Get(); + if (ev->Request->ResultSet.size() == 1 && ev->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { + TSchemeCacheNavigate::TEntry& entry(ev->Request->ResultSet.front()); + if (entry.Self) { + FilterPathId = TPathId(entry.Self->Info.GetSchemeshardId(), entry.Self->Info.GetPathId()); + AskHiveAboutPaths = true; + HivesToAsk.push_back(AppData()->DomainsInfo->GetHive()); + if (entry.DomainInfo) { + const auto ownerId = entry.DomainInfo->DomainKey.OwnerId; + const auto localPathId = entry.DomainInfo->DomainKey.LocalPathId; + SubDomainKey = TSubDomainKey(ownerId, localPathId); + if (FilterDatabase) { + FilterSubDomainKey = true; + } + if (entry.DomainInfo->Params.HasHive()) { + HivesToAsk.push_back(entry.DomainInfo->Params.GetHive()); + } + } + } + } + } else { + AddProblem("no-path-info"); + } + PathNavigateResponse.reset(); + } + + if (DatabaseBoardInfoResponse && DatabaseBoardInfoResponse->IsDone() && TotalNodes > 0) { + if (DatabaseBoardInfoResponse->IsOk() && DatabaseBoardInfoResponse->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { + TString database = GetDatabaseFromEndpointsBoardPath(DatabaseBoardInfoResponse->Get()->Path); + for (const auto& entry : DatabaseBoardInfoResponse->Get()->InfoEntries) { + if (!entry.second.Dropped) { + TNode* node = FindNode(entry.first.NodeId()); + if (node) { + node->Database = database; + node->GotDatabaseFromDatabaseBoardInfo = true; + HasDatabaseNodes = true; + } + } + } + FieldsAvailable.set(+ENodeFields::Database); + } else { + AddProblem("no-database-board-info"); + } + DatabaseBoardInfoResponse.reset(); + } + + if (ResourceBoardInfoResponse && ResourceBoardInfoResponse->IsDone() && TotalNodes > 0) { + if (ResourceBoardInfoResponse->IsOk() && ResourceBoardInfoResponse->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { + TString database = GetDatabaseFromEndpointsBoardPath(ResourceBoardInfoResponse->Get()->Path); + for (const auto& entry : ResourceBoardInfoResponse->Get()->InfoEntries) { + if (!entry.second.Dropped) { + TNode* node = FindNode(entry.first.NodeId()); + if (node) { + node->Database = database; + node->GotDatabaseFromResourceBoardInfo = true; + } + } + } + FieldsAvailable.set(+ENodeFields::Database); + } else { + AddProblem("no-shared-database-board-info"); + } + ResourceBoardInfoResponse.reset(); + } + + if (TimeToAskHive() && !HivesToAsk.empty()) { + AddEvent("TimeToAskHive"); + std::sort(HivesToAsk.begin(), HivesToAsk.end()); + HivesToAsk.erase(std::unique(HivesToAsk.begin(), HivesToAsk.end()), HivesToAsk.end()); + for (TTabletId hiveId : HivesToAsk) { + auto request = std::make_unique(); + request->Record.SetReturnMetrics(true); + if (Database) { // it's better to ask hive about tablets only if we're filtering by database + request->Record.SetReturnExtendedTabletInfo(true); + } + if (AskHiveAboutPaths) { + request->Record.SetFilterTabletsBySchemeShardId(FilterPathId.OwnerId); + request->Record.SetFilterTabletsByPathId(FilterPathId.LocalPathId); + } + HiveNodeStats.emplace(hiveId, MakeRequestHiveNodeStats(hiveId, request.release())); + } + HivesToAsk.clear(); + } + + if (HiveResponsesDone()) { + AddEvent("HiveResponsesDone"); + for (const auto& [hiveId, nodeStats] : HiveNodeStats) { + if (nodeStats.IsDone()) { + if (nodeStats.IsOk()) { + TInstant now = TInstant::Now(); + for (const NKikimrHive::THiveNodeStats& nodeStats : nodeStats.Get()->Record.GetNodeStats()) { + ui32 nodeId = nodeStats.GetNodeId(); + TNode* node = FindNode(nodeId); + if (node) { + if (Database) { // it's better to ask hive about tablets only if we're filtering by database + for (const NKikimrHive::THiveDomainStatsStateCount& stateStats : nodeStats.GetStateStats()) { + NKikimrViewer::TTabletStateInfo& viewerTablet(node->Tablets.emplace_back()); + viewerTablet.SetType(NKikimrTabletBase::TTabletTypes::EType_Name(stateStats.GetTabletType())); + viewerTablet.SetCount(stateStats.GetCount()); + viewerTablet.SetState(GetFlagFromTabletState(stateStats.GetVolatileState())); + FieldsAvailable.set(+ENodeFields::Tablets); + } + } + if (nodeStats.HasLastAliveTimestamp()) { + node->SystemState.SetDisconnectTime(std::max(node->SystemState.GetDisconnectTime(), nodeStats.GetLastAliveTimestamp())); // milliseconds + node->CalcUptimeSeconds(now); + FieldsAvailable.set(+ENodeFields::DisconnectTime); + } + if (nodeStats.HasNodeDomain()) { + node->SubDomainKey = TSubDomainKey(nodeStats.GetNodeDomain()); + FieldsAvailable.set(+ENodeFields::SubDomainKey); + if (node->SubDomainKey == SubDomainKey) { + HasDatabaseNodes = true; + } + } + } + } + } else { + AddProblem("hive-no-data"); + } + } + } + HiveNodeStats.clear(); + } + + if (FilterStorageStage == EFilterStorageStage::Pools && StoragePoolsResponse && StoragePoolsResponse->IsDone()) { + if (StoragePoolsResponse->IsOk()) { + for (const auto& storagePoolEntry : StoragePoolsResponse->Get()->Record.GetEntries()) { + if (storagePoolEntry.GetInfo().GetName() == FilterStoragePool) { + FilterStoragePoolId = {storagePoolEntry.GetKey().GetBoxId(), storagePoolEntry.GetKey().GetStoragePoolId()}; + break; + } + } + FilterStorageStage = EFilterStorageStage::Groups; + } else { + AddProblem("bsc-storage-pools-no-data"); + } + StoragePoolsResponse.reset(); + } + if (FilterStorageStage == EFilterStorageStage::Groups && GroupsResponse && GroupsResponse->IsDone()) { + if (GroupsResponse->IsOk()) { + for (const auto& groupEntry : GroupsResponse->Get()->Record.GetEntries()) { + if (groupEntry.GetInfo().GetBoxId() == FilterStoragePoolId.first + && groupEntry.GetInfo().GetStoragePoolId() == FilterStoragePoolId.second) { + FilterGroupIds.insert(groupEntry.GetKey().GetGroupId()); + } + } + FilterStorageStage = EFilterStorageStage::VSlots; + } else { + AddProblem("bsc-storage-groups-no-data"); + } + GroupsResponse.reset(); + } + if (FilterStorageStage == EFilterStorageStage::VSlots && VSlotsResponse && VSlotsResponse->IsDone()) { + if (VSlotsResponse->IsOk()) { + std::unordered_set prevFilterNodeIds = std::move(FilterNodeIds); + std::unordered_map, std::size_t> slotsPerDisk; + for (const auto& slotEntry : VSlotsResponse->Get()->Record.GetEntries()) { + if (FilterGroupIds.count(slotEntry.GetInfo().GetGroupId()) > 0) { + if (prevFilterNodeIds.empty() || prevFilterNodeIds.count(slotEntry.GetKey().GetNodeId()) > 0) { + FilterNodeIds.insert(slotEntry.GetKey().GetNodeId()); + } + TNode* node = FindNode(slotEntry.GetKey().GetNodeId()); + if (node) { + node->SysViewVDisks.emplace_back(slotEntry); + node->HasDisks = true; + } + } + TNode* node = FindNode(slotEntry.GetKey().GetNodeId()); + if (node) { + node->HasDisks = true; + } + auto& slots = slotsPerDisk[{slotEntry.GetKey().GetNodeId(), slotEntry.GetKey().GetPDiskId()}]; + ++slots; + MaximumSlotsPerDisk = std::max(MaximumSlotsPerDisk.value_or(0), slots); + } + FieldsAvailable.set(+ENodeFields::HasDisks); + FilterStorageStage = EFilterStorageStage::None; + ApplyEverything(); + } else { + AddProblem("bsc-storage-slots-no-data"); + } + VSlotsResponse.reset(); + } + if (PDisksResponse && PDisksResponse->IsDone()) { + if (PDisksResponse->IsOk()) { + std::unordered_map disksPerNode; + for (const auto& pdiskEntry : PDisksResponse->Get()->Record.GetEntries()) { + TNode* node = FindNode(pdiskEntry.GetKey().GetNodeId()); + if (node) { + node->SysViewPDisks.emplace_back(pdiskEntry); + node->HasDisks = true; + } + auto& disks = disksPerNode[pdiskEntry.GetKey().GetNodeId()]; + ++disks; + MaximumDisksPerNode = std::max(MaximumDisksPerNode.value_or(0), disks); + } + for (TNode* node : NodeView) { + node->CalcDisks(); + } + FieldsAvailable.set(+ENodeFields::HasDisks); + FieldsAvailable.set(+ENodeFields::Missing); + FieldsAvailable.set(+ENodeFields::DiskSpaceUsage); + } else { + AddProblem("bsc-pdisks-no-data"); + } + PDisksResponse.reset(); + } + + if (TimeToAskWhiteboard() && FieldsAvailable.test(+ENodeFields::NodeInfo)) { + AddEvent("TimeToAskWhiteboard"); + ApplyEverything(); + if (FilterDatabase) { + FieldsRequired.set(+ENodeFields::SystemState); + } + std::vector batches = BatchNodes(); + SendWhiteboardRequests(batches); + } + } + + template + void InitWhiteboardRequest(TWhiteboardEvent* request) { + if (AllWhiteboardFields) { + request->AddFieldsRequired(-1); + } + } + + void SendWhiteboardSystemAndTabletsBatch(TNodeBatch& batch) { + TNodeId nodeId = OffloadMerge ? batch.ChooseNodeId() : 0; + if (batch.HasStaticNodes && (FieldsNeeded(FieldsVDisks) || FieldsNeeded(FieldsPDisks))) { + nodeId = 0; // we need to ask for all nodes anyway (for the compatibility with older versions) + } + if (nodeId) { + if (FieldsNeeded(FieldsSystemState) && SystemViewerResponse.count(nodeId) == 0) { + auto viewerRequest = std::make_unique(); + InitWhiteboardRequest(viewerRequest->Record.MutableSystemRequest()); + viewerRequest->Record.SetTimeout(Timeout / 2); + for (const TNode* node : batch.NodesToAskAbout) { + viewerRequest->Record.MutableLocation()->AddNodeId(node->GetNodeId()); + } + SystemViewerResponse.emplace(nodeId, MakeViewerRequest(nodeId, viewerRequest.release())); + NodeBatches.emplace(nodeId, batch); + ++WhiteboardStateRequestsInFlight; + } + if (FieldsNeeded(FieldsTablets) && TabletViewerResponse.count(nodeId) == 0) { + auto viewerRequest = std::make_unique(); + viewerRequest->Record.MutableTabletRequest()->SetGroupBy("Type,State"); + viewerRequest->Record.SetTimeout(Timeout / 2); + for (const TNode* node : batch.NodesToAskAbout) { + viewerRequest->Record.MutableLocation()->AddNodeId(node->GetNodeId()); + } + TabletViewerResponse.emplace(nodeId, MakeViewerRequest(nodeId, viewerRequest.release())); + NodeBatches.emplace(nodeId, batch); // ignore second insert because they are the same + ++WhiteboardStateRequestsInFlight; + } + } else { + for (const TNode* node : batch.NodesToAskAbout) { + if (node->Disconnected) { + continue; + } + TNodeId nodeId = node->GetNodeId(); + if (FieldsNeeded(FieldsSystemState)) { + if (SystemStateResponse.count(nodeId) == 0) { + auto request = new TEvWhiteboard::TEvSystemStateRequest(); + InitWhiteboardRequest(&request->Record); + SystemStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request)); + ++WhiteboardStateRequestsInFlight; + } + } + if (FieldsNeeded(FieldsTablets)) { + if (TabletStateResponse.count(nodeId) == 0) { + auto request = std::make_unique(); + request->Record.SetGroupBy("Type,State"); + TabletStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request.release())); + ++WhiteboardStateRequestsInFlight; + } + } + } + } + } + + void SendWhiteboardRequest(TNodeBatch& batch) { + SendWhiteboardSystemAndTabletsBatch(batch); + for (const TNode* node : batch.NodesToAskAbout) { + TNodeId nodeId = node->GetNodeId(); + + if (node->IsStatic()) { + if (FieldsNeeded(FieldsVDisks)) { + if (VDiskStateResponse.count(nodeId) == 0) { + auto request = new TEvWhiteboard::TEvVDiskStateRequest(); + InitWhiteboardRequest(&request->Record); + VDiskStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request)); + ++WhiteboardStateRequestsInFlight; + } + } + if (FieldsNeeded(FieldsPDisks)) { + if (PDiskStateResponse.count(nodeId) == 0) { + auto request = new TEvWhiteboard::TEvPDiskStateRequest(); + InitWhiteboardRequest(&request->Record); + PDiskStateResponse.emplace(nodeId, MakeWhiteboardRequest(nodeId, request)); + ++WhiteboardStateRequestsInFlight; + } + } + } + } + } + + void SendWhiteboardRequests(std::vector& batches) { + for (TNodeBatch& batch : batches) { + SendWhiteboardRequest(batch); + } + } + + void ProcessWhiteboard() { + if (FieldsNeeded(FieldsSystemState)) { + TInstant now = TInstant::Now(); + std::unordered_set removeNodes; + for (const auto& [responseNodeId, response] : SystemViewerResponse) { + if (response.IsOk()) { + const auto& systemResponse(response.Get()->Record.GetSystemResponse()); + std::unordered_set nodesWithoutData; + for (auto nodeId : response.Get()->Record.GetLocationResponded().GetNodeId()) { + nodesWithoutData.insert(nodeId); + } + for (const auto& systemInfo : systemResponse.GetSystemStateInfo()) { + TNodeId nodeId = systemInfo.GetNodeId(); + TNode* node = FindNode(nodeId); + if (node) { + nodesWithoutData.erase(nodeId); + node->MergeFrom(systemInfo, now); + if (Database && node->Database) { + if (node->Database != Database && (!SharedDatabase || node->Database != SharedDatabase)) { + removeNodes.insert(nodeId); + } + } + } + } + for (auto nodeId : nodesWithoutData) { + TNode* node = FindNode(nodeId); + if (node) { + node->DisconnectNode(); + } + } + } else { + TNode* node = FindNode(responseNodeId); + if (node) { + node->DisconnectNode(); + } + } + } + for (const auto& [nodeId, response] : SystemStateResponse) { + if (response.IsOk()) { + const auto& systemState(response.Get()->Record); + if (systemState.SystemStateInfoSize() > 0) { + TNode* node = FindNode(nodeId); + if (node) { + node->MergeFrom(systemState.GetSystemStateInfo(0), now); + if (Database && node->Database) { + if (node->Database != Database && (!SharedDatabase || node->Database != SharedDatabase)) { + removeNodes.insert(nodeId); + } + } + } + } + } else { + TNode* node = FindNode(nodeId); + if (node) { + node->DisconnectNode(); + } + } + } + if (!removeNodes.empty()) { + NodeView.erase(std::remove_if(NodeView.begin(), NodeView.end(), [&removeNodes](const TNode* node) { return removeNodes.count(node->GetNodeId()) > 0; }), NodeView.end()); + TotalNodes = FoundNodes = NodeView.size(); + InvalidateNodes(); + } + FieldsAvailable |= FieldsSystemState; + FieldsAvailable.set(+ENodeFields::Database); + } + if (FieldsNeeded(FieldsTablets)) { + for (auto& [nodeId, response] : TabletViewerResponse) { + if (response.IsOk()) { + auto& tabletResponse(*(response.Get()->Record.MutableTabletResponse())); + if (tabletResponse.TabletStateInfoSize() > 0 && !tabletResponse.GetTabletStateInfo(0).HasCount()) { + GroupWhiteboardResponses(tabletResponse, "NodeId,Type,State"); + } + for (const auto& tabletState : tabletResponse.GetTabletStateInfo()) { + TNode* node = FindNode(tabletState.GetNodeId()); + if (node) { + if (tabletState.GetState() != NKikimrWhiteboard::TTabletStateInfo::Dead) { + NKikimrViewer::TTabletStateInfo& viewerTablet(node->Tablets.emplace_back()); + viewerTablet.SetType(NKikimrTabletBase::TTabletTypes::EType_Name(tabletState.GetType())); + viewerTablet.SetState(GetFlagFromTabletState(tabletState.GetState())); + viewerTablet.SetCount(tabletState.GetCount()); + } + } + } + } + } + for (auto& [nodeId, response] : TabletStateResponse) { + if (response.IsOk()) { + const auto& tabletState(response.Get()->Record); + TNode* node = FindNode(nodeId); + if (node) { + for (const auto& protoTabletState : tabletState.GetTabletStateInfo()) { + if (protoTabletState.GetState() != NKikimrWhiteboard::TTabletStateInfo::Dead) { + NKikimrViewer::TTabletStateInfo& viewerTablet(node->Tablets.emplace_back()); + viewerTablet.SetType(NKikimrTabletBase::TTabletTypes::EType_Name(protoTabletState.GetType())); + viewerTablet.SetState(GetFlagFromTabletState(protoTabletState.GetState())); + viewerTablet.SetCount(protoTabletState.GetCount()); + } + } + } + } + } + FieldsAvailable |= FieldsTablets; + } + if (FieldsNeeded(FieldsVDisks)) { + for (const auto& [nodeId, response] : VDiskStateResponse) { + if (response.IsOk()) { + const auto& vDiskState(response.Get()->Record); + TNode* node = FindNode(nodeId); + if (node) { + for (const auto& protoVDiskState : vDiskState.GetVDiskStateInfo()) { + node->VDisks.emplace_back(protoVDiskState); + } + } + } + } + FieldsAvailable |= FieldsVDisks; + } + if (FieldsNeeded(FieldsPDisks)) { + for (const auto& [nodeId, response] : PDiskStateResponse) { + if (response.IsOk()) { + const auto& pDiskState(response.Get()->Record); + TNode* node = FindNode(nodeId); + if (node) { + for (const auto& protoPDiskState : pDiskState.GetPDiskStateInfo()) { + node->PDisks.emplace_back(protoPDiskState); + } + node->CalcDisks(); + } + MaximumDisksPerNode = std::max(MaximumDisksPerNode.value_or(0), pDiskState.PDiskStateInfoSize()); + } + } + FieldsAvailable |= FieldsPDisks; + FieldsAvailable.set(+ENodeFields::Missing); + FieldsAvailable.set(+ENodeFields::DiskSpaceUsage); + } + ApplyEverything(); + } + + void Handle(TEvInterconnect::TEvNodesInfo::TPtr& ev) { + NodesInfoResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvWhiteboard::TEvNodeStateResponse::TPtr& ev) { + NodeStateResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + if (ev->Cookie == ENavigateRequestDatabase) { + DatabaseNavigateResponse->Set(std::move(ev)); + } else if (ev->Cookie == ENavigateRequestResource) { + ResourceNavigateResponse->Set(std::move(ev)); + } else if (ev->Cookie == ENavigateRequestPath) { + PathNavigateResponse->Set(std::move(ev)); + } + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { + if (ev->Cookie == EBoardInfoRequestDatabase) { + DatabaseBoardInfoResponse->Set(std::move(ev)); + } else if (ev->Cookie == EBoardInfoRequestResource) { + ResourceBoardInfoResponse->Set(std::move(ev)); + } + ProcessResponses(); + RequestDone(); + } + + void Handle(TEvHive::TEvResponseHiveNodeStats::TPtr& ev) { + HiveNodeStats[ev->Cookie].Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void WhiteboardRequestDone() { + --WhiteboardStateRequestsInFlight; + if (WhiteboardStateRequestsInFlight == 0) { + ProcessWhiteboard(); + } + RequestDone(); + } + + void Handle(TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + SystemStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvWhiteboard::TEvVDiskStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + VDiskStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvWhiteboard::TEvPDiskStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + PDiskStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvWhiteboard::TEvTabletStateResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + TabletStateResponse[nodeId].Set(std::move(ev)); + WhiteboardRequestDone(); + } + + void Handle(TEvViewer::TEvViewerResponse::TPtr& ev) { + ui64 nodeId = ev.Get()->Cookie; + switch (ev->Get()->Record.Response_case()) { + case NKikimrViewer::TEvViewerResponse::ResponseCase::kSystemResponse: + SystemViewerResponse[nodeId].Set(std::move(ev)); + NodeBatches.erase(nodeId); + WhiteboardRequestDone(); + return; + case NKikimrViewer::TEvViewerResponse::ResponseCase::kTabletResponse: + TabletViewerResponse[nodeId].Set(std::move(ev)); + NodeBatches.erase(nodeId); + WhiteboardRequestDone(); + return; + default: + break; + } + TString error("WrongResponse"); + { + auto itSystemViewerResponse = SystemViewerResponse.find(nodeId); + if (itSystemViewerResponse != SystemViewerResponse.end()) { + if (itSystemViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardSystemAndTabletsBatch(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + { + auto itTabletViewerResponse = TabletViewerResponse.find(nodeId); + if (itTabletViewerResponse != TabletViewerResponse.end()) { + if (itTabletViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardSystemAndTabletsBatch(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + } + + void Handle(NSysView::TEvSysView::TEvGetStoragePoolsResponse::TPtr& ev) { + StoragePoolsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetGroupsResponse::TPtr& ev) { + GroupsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetVSlotsResponse::TPtr& ev) { + VSlotsResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Handle(NSysView::TEvSysView::TEvGetPDisksResponse::TPtr& ev) { + PDisksResponse->Set(std::move(ev)); + ProcessResponses(); + RequestDone(); + } + + void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { + TNodeId nodeId = ev->Get()->NodeId; + TNode* node = FindNode(nodeId); + if (node) { + node->DisconnectNode(); + if (FieldsRequired.test(+ENodeFields::PDisks) || FieldsRequired.test(+ENodeFields::VDisks)) { + node->RemapDisks(); + } + node->CalcUptimeSeconds(TInstant::Now()); + } + TString error("NodeDisconnected"); + { + auto itSystemStateResponse = SystemStateResponse.find(nodeId); + if (itSystemStateResponse != SystemStateResponse.end()) { + if (itSystemStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itVDiskStateResponse = VDiskStateResponse.find(nodeId); + if (itVDiskStateResponse != VDiskStateResponse.end()) { + if (itVDiskStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itPDiskStateResponse = PDiskStateResponse.find(nodeId); + if (itPDiskStateResponse != PDiskStateResponse.end()) { + if (itPDiskStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itTabletStateResponse = TabletStateResponse.find(nodeId); + if (itTabletStateResponse != TabletStateResponse.end()) { + if (itTabletStateResponse->second.Error(error)) { + WhiteboardRequestDone(); + } + } + } + { + auto itSystemViewerResponse = SystemViewerResponse.find(nodeId); + if (itSystemViewerResponse != SystemViewerResponse.end()) { + if (itSystemViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardSystemAndTabletsBatch(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + { + auto itTabletViewerResponse = TabletViewerResponse.find(nodeId); + if (itTabletViewerResponse != TabletViewerResponse.end()) { + if (itTabletViewerResponse->second.Error(error)) { + if (NodeBatches.count(nodeId)) { + SendWhiteboardSystemAndTabletsBatch(NodeBatches[nodeId]); + NodeBatches.erase(nodeId); + } + WhiteboardRequestDone(); + } + } + } + } + + bool OnBscError(const TString& error) { + bool result = false; + if (StoragePoolsResponse && StoragePoolsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + result = true; + } + if (GroupsResponse && GroupsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + result = true; + } + if (VSlotsResponse && VSlotsResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + result = true; + } + if (PDisksResponse && PDisksResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + result = true; + } + return result; + } + + void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { + if (ev->Get()->Status != NKikimrProto::OK) { + TString error = TStringBuilder() << "Failed to establish pipe to " << ev->Get()->TabletId << ": " + << NKikimrProto::EReplyStatus_Name(ev->Get()->Status); + auto it = HiveNodeStats.find(ev->Get()->TabletId); + if (it != HiveNodeStats.end()) { + if (it->second.Error(error)) { + AddProblem("hive-error"); + ProcessResponses(); + RequestDone(); + } + } + if (ev->Get()->TabletId == GetBSControllerId()) { + if (OnBscError(error)) { + AddProblem("bsc-error"); + } + } + FailPipeConnect(ev->Get()->TabletId); + } + } + + void HandleTimeout(TEvents::TEvWakeup::TPtr& ev) { + CurrentTimeoutState = static_cast(ev->Get()->Tag); + TString error = "Timeout"; + if (ev->Get()->Tag == TimeoutTablets) { + if (NodesInfoResponse && NodesInfoResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (NodeStateResponse && NodeStateResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (DatabaseNavigateResponse && DatabaseNavigateResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (ResourceNavigateResponse && ResourceNavigateResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (PathNavigateResponse && PathNavigateResponse->Error(error)) { + ProcessResponses(); + RequestDone(); + } + if (OnBscError(error)) { + AddProblem("bsc-timeout"); + FailPipeConnect(GetBSControllerId()); + } + for (auto& [hiveId, response] : HiveNodeStats) { + if (response.Error(error)) { + AddProblem("hive-timeout"); + ProcessResponses(); + RequestDone(); + FailPipeConnect(hiveId); + } + } + } + if (ev->Get()->Tag == TimeoutFinal) { + for (auto& [nodeId, response] : SystemViewerResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + for (auto& [nodeId, response] : TabletViewerResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + for (auto& [nodeId, response] : SystemStateResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + for (auto& [nodeId, response] : VDiskStateResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + for (auto& [nodeId, response] : PDiskStateResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + for (auto& [nodeId, response] : TabletStateResponse) { + if (response.Error(error)) { + AddProblem("wb-incomplete"); + WhiteboardRequestDone(); + } + } + if (WaitingForResponse()) { + ReplyAndPassAway(); + } + } + } + + STATEFN(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvInterconnect::TEvNodesInfo, Handle); + hFunc(TEvWhiteboard::TEvNodeStateResponse, Handle); + hFunc(TEvWhiteboard::TEvSystemStateResponse, Handle); + hFunc(TEvWhiteboard::TEvPDiskStateResponse, Handle); + hFunc(TEvWhiteboard::TEvVDiskStateResponse, Handle); + hFunc(TEvWhiteboard::TEvTabletStateResponse, Handle); + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + hFunc(TEvStateStorage::TEvBoardInfo, Handle); + hFunc(TEvHive::TEvResponseHiveNodeStats, Handle); + hFunc(NSysView::TEvSysView::TEvGetGroupsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetStoragePoolsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetVSlotsResponse, Handle); + hFunc(NSysView::TEvSysView::TEvGetPDisksResponse, Handle); + hFunc(TEvViewer::TEvViewerResponse, Handle); + hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); + hFunc(TEvTabletPipe::TEvClientConnected, Handle); + hFunc(TEvents::TEvWakeup, HandleTimeout); + } + } + + void ReplyAndPassAway() override { + AddEvent("ReplyAndPassAway"); + ApplyEverything(); + NKikimrViewer::TNodesInfo json; + json.SetVersion(Viewer->GetCapabilityVersion("/viewer/nodes")); + json.SetFieldsAvailable(FieldsAvailable.to_string()); + json.SetFieldsRequired(FieldsRequired.to_string()); + if (NeedFilter) { + json.SetNeedFilter(true); + } + if (NeedGroup) { + json.SetNeedGroup(true); + } + if (NeedSort) { + json.SetNeedSort(true); + } + if (NeedLimit) { + json.SetNeedLimit(true); + } + json.SetTotalNodes(TotalNodes); + json.SetFoundNodes(FoundNodes); + if (MaximumDisksPerNode.has_value()) { + json.SetMaximumDisksPerNode(MaximumDisksPerNode.value()); + } + if (MaximumSlotsPerDisk.has_value()) { + json.SetMaximumSlotsPerDisk(MaximumSlotsPerDisk.value()); + } + if (NoDC) { + json.SetNoDC(true); + } + if (NoRack) { + json.SetNoRack(true); + } + for (auto problem : Problems) { + json.AddProblems(problem); + } + if (NodeGroups.empty()) { + for (TNode* node : NodeView) { + NKikimrViewer::TNodeInfo& jsonNode = *json.AddNodes(); + if (FieldsAvailable.test(+ENodeFields::NodeInfo)) { + jsonNode.SetNodeId(node->GetNodeId()); + } + if (node->Database) { + jsonNode.SetDatabase(node->Database); + } + if (node->UptimeSeconds) { + jsonNode.SetUptimeSeconds(node->UptimeSeconds); + } + if (node->Disconnected) { + jsonNode.SetDisconnected(node->Disconnected); + } + if (node->CpuUsage) { + jsonNode.SetCpuUsage(node->CpuUsage); + } + if (node->DiskSpaceUsage) { + jsonNode.SetDiskSpaceUsage(node->DiskSpaceUsage); + } + if (FieldsAvailable.test(+ENodeFields::NodeInfo) || FieldsAvailable.test(+ENodeFields::SystemState)) { + *jsonNode.MutableSystemState() = std::move(node->SystemState); + } + if (FieldsAvailable.test(+ENodeFields::PDisks)) { + std::sort(node->PDisks.begin(), node->PDisks.end(), [](const NKikimrWhiteboard::TPDiskStateInfo& a, const NKikimrWhiteboard::TPDiskStateInfo& b) { + return a.path() < b.path(); + }); + for (NKikimrWhiteboard::TPDiskStateInfo& pDisk : node->PDisks) { + (*jsonNode.AddPDisks()) = std::move(pDisk); + } + } + if (FieldsAvailable.test(+ENodeFields::VDisks)) { + std::sort(node->VDisks.begin(), node->VDisks.end(), [](const NKikimrWhiteboard::TVDiskStateInfo& a, const NKikimrWhiteboard::TVDiskStateInfo& b) { + return VDiskIDFromVDiskID(a.vdiskid()) < VDiskIDFromVDiskID(b.vdiskid()); + }); + for (NKikimrWhiteboard::TVDiskStateInfo& vDisk : node->VDisks) { + (*jsonNode.AddVDisks()) = std::move(vDisk); + } + } + if (FieldsAvailable.test(+ENodeFields::Tablets)) { + std::sort(node->Tablets.begin(), node->Tablets.end(), [](const NKikimrViewer::TTabletStateInfo& a, const NKikimrViewer::TTabletStateInfo& b) { + return a.type() < b.type(); + }); + for (NKikimrViewer::TTabletStateInfo& tablet : node->Tablets) { + (*jsonNode.AddTablets()) = std::move(tablet); + } + } + } + } else { + for (const TNodeGroup& nodeGroup : NodeGroups) { + NKikimrViewer::TNodeGroup& jsonNodeGroup = *json.AddNodeGroups(); + jsonNodeGroup.SetGroupName(nodeGroup.Name); + jsonNodeGroup.SetNodeCount(nodeGroup.Nodes.size()); + } + } + AddEvent("RenderingResult"); + TStringStream out; + Proto2Json(json, out, { + .EnumMode = TProto2JsonConfig::EnumValueMode::EnumName, + .MapAsObject = true, + .StringifyNumbers = TProto2JsonConfig::EStringifyNumbersMode::StringifyInt64Always, + .WriteNanAsString = true, + }); + AddEvent("ResultReady"); + TBase::ReplyAndPassAway(GetHTTPOKJSON(out.Str())); + } + + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( + get: + tags: + - viewer + summary: Nodes info + description: Information about nodes + parameters: + - name: database + in: query + description: database name + required: false + type: string + - name: path + in: query + description: path to schema object + required: false + type: string + - name: node_id + in: query + description: node id + required: false + type: integer + - name: group_id + in: query + description: group id + required: false + type: integer + - name: pool + in: query + description: storage pool name + required: false + type: string + - name: type + in: query + description: > + return nodes of specific type: + * `static` + * `dynamic` + * `storage` + * `any` + - name: with + in: query + description: > + filter groups by missing or space: + * `missing` + * `space` + - name: storage + in: query + description: return storage info + required: false + type: boolean + - name: tablets + in: query + description: return tablets info + required: false + type: boolean + - name: problems_only + in: query + description: return only problem nodes + required: false + type: boolean + - name: uptime + in: query + description: return only nodes with less uptime in sec. + required: false + type: integer + - name: filter + in: query + description: filter nodes by id or host + required: false + type: string + - name: sort + in: query + description: > + sort by: + * `NodeId` + * `Host` + * `NodeName` + * `DC` + * `Rack` + * `Version` + * `Uptime` + * `Memory` + * `CPU` + * `LoadAverage` + * `Missing` + * `DiskSpaceUsage` + * `Database` + required: false + type: string + - name: group + in: query + description: > + group by: + * `NodeId` + * `Host` + * `NodeName` + * `Database` + * `DiskSpaceUsage` + * `DC` + * `Rack` + * `Missing` + * `Uptime` + * `Version` + required: false + type: string + - name: filter_group_by + in: query + description: > + group by: + * `NodeId` + * `Host` + * `NodeName` + * `Database` + * `DiskSpaceUsage` + * `DC` + * `Rack` + * `Missing` + * `Uptime` + * `Version` + required: false + type: string + - name: filter_group + in: query + description: content for filter group by + required: false + type: string + - name: fields_required + in: query + description: > + list of fields required in response, the more - the heavier could be request. `all` for all fields: + * `NodeId` (always required) + * `SystemState` + * `PDisks` + * `VDisks` + * `Tablets` + * `Host` + * `NodeName` + * `DC` + * `Rack` + * `Version` + * `Uptime` + * `Memory` + * `CPU` + * `LoadAverage` + * `Missing` + * `DiskSpaceUsage` + * `SubDomainKey` + * `DisconnectTime` + * `Database` + required: false + type: string + - name: offset + in: query + description: skip N nodes + required: false + type: integer + - name: limit + in: query + description: limit to N nodes + required: false + type: integer + - name: timeout + in: query + description: timeout in ms + required: false + type: integer + - name: direct + in: query + description: fulfill request on the target node without redirects + required: false + type: boolean + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + node["get"]["responses"]["200"]["content"]["application/json"]["schema"] = TProtoToYaml::ProtoToYamlSchema(); + return node; + } +}; + +} diff --git a/ydb/core/viewer/json_pdiskinfo.h b/ydb/core/viewer/viewer_pdiskinfo.h similarity index 69% rename from ydb/core/viewer/json_pdiskinfo.h rename to ydb/core/viewer/viewer_pdiskinfo.h index 5a2ff46edc2f..7c85393e667b 100644 --- a/ydb/core/viewer/json_pdiskinfo.h +++ b/ydb/core/viewer/viewer_pdiskinfo.h @@ -1,13 +1,7 @@ #pragma once -#include -#include -#include -#include -#include #include "json_wb_req.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { template <> struct TWhiteboardInfo { @@ -41,19 +35,4 @@ struct TWhiteboardInfo { using TJsonPDiskInfo = TJsonWhiteboardRequest; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "PDisk information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns PDisk information"; - } -}; - -} } diff --git a/ydb/core/viewer/json_pqconsumerinfo.h b/ydb/core/viewer/viewer_pqconsumerinfo.h similarity index 65% rename from ydb/core/viewer/json_pqconsumerinfo.h rename to ydb/core/viewer/viewer_pqconsumerinfo.h index 0071ec99e8bd..88cc9d305f1c 100644 --- a/ydb/core/viewer/json_pqconsumerinfo.h +++ b/ydb/core/viewer/viewer_pqconsumerinfo.h @@ -1,20 +1,14 @@ #pragma once -#include -#include -#include -#include -#include +#include "json_handlers.h" +#include "viewer.h" #include -#include +#include #include -#include "viewer.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; - class TJsonPQConsumerInfo : public TActorBootstrapped { using TBase = TActorBootstrapped; IViewer* Viewer; @@ -115,76 +109,54 @@ class TJsonPQConsumerInfo : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: topic - in: query - description: topic name - required: true - type: string - - name: dc - in: query - description: dc name (required with version >= 3) - required: false - type: string - default: "" - - name: version - in: query - description: query version - required: false - type: integer - default: 0 - - name: client - in: query - description: client name - required: true - type: string - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - default: false - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - default: false - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - default: 10000 - )___"); - } -}; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Consumer-topic metrics"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Consumer-topic metrics", + .Description = "Returns consumer-topic metrics", + }); + yaml.AddParameter({ + .Name = "topic", + .Description = "topic name", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "dc", + .Description = "dc name (required with version >= 3)", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "version", + .Description = "query version", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "client", + .Description = "client name", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns consumer-topic metrics"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_probes.cpp b/ydb/core/viewer/viewer_probes.cpp deleted file mode 100644 index 616d63e91e39..000000000000 --- a/ydb/core/viewer/viewer_probes.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "viewer_probes.h" -LWTRACE_DEFINE_PROVIDER(VIEWER_PROVIDER) diff --git a/ydb/core/viewer/viewer_probes.h b/ydb/core/viewer/viewer_probes.h deleted file mode 100644 index 3ce88fc06f1a..000000000000 --- a/ydb/core/viewer/viewer_probes.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include - -#define VIEWER_PROVIDER(PROBE, EVENT, GROUPS, TYPES, NAMES) \ - PROBE(ViewerClusterHandler, \ - GROUPS(), \ - TYPES(ui32, bool, bool, \ - double, double, double, double, \ - double, double, double, double, \ - double, double), \ - NAMES("nodeId", "tabletsParam", "requestTimeout", \ - "startTime", "totalDurationMs", \ - "getListTenantsResponseDurationMs","getNodesInfoDurationMs", \ - "collectingDurationMs", "mergeBSGroupsDurationMs", \ - "mergeVDisksDurationMs", "mergePDisksDurationMs", \ - "mergeTabletsDurationMs", "responseBuildingDurationMs")) \ -/**/ -LWTRACE_DECLARE_PROVIDER(VIEWER_PROVIDER) diff --git a/ydb/core/viewer/json_query.h b/ydb/core/viewer/viewer_query.h similarity index 60% rename from ydb/core/viewer/json_query.h rename to ydb/core/viewer/viewer_query.h index dabfe440f714..c875dc0ba15b 100644 --- a/ydb/core/viewer/json_query.h +++ b/ydb/core/viewer/viewer_query.h @@ -1,44 +1,36 @@ #pragma once -#include "viewer.h" -#include -#include -#include -#include -#include -#include -#include +#include "json_handlers.h" +#include "json_pipe_req.h" +#include "log.h" #include #include #include -#include #include #include -#include "json_pipe_req.h" -#include "viewer_request.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NMonitoring; -using ::google::protobuf::FieldDescriptor; using namespace NNodeWhiteboard; -class TJsonQuery : public TViewerPipeClient { +bool IsPostContent(const NMon::TEvHttpInfo::TPtr& event); + +class TJsonQuery : public TViewerPipeClient { using TThis = TJsonQuery; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; TJsonSettings JsonSettings; ui32 Timeout = 0; - TVector ResultSets; + std::vector> ResultSets; TString Query; - TString Database; TString Action; TString Stats; TString Syntax; TString QueryId; TString TransactionMode; - bool Direct = false; bool IsBase64Encode = true; + int LimitRows = 10000; + int TotalRows = 0; enum ESchemaType { Classic, @@ -52,10 +44,6 @@ class TJsonQuery : public TViewerPipeClient { TString SessionId; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - ESchemaType StringToSchemaType(const TString& schemaStr) { if (schemaStr == "classic") { return ESchemaType::Classic; @@ -74,31 +62,74 @@ class TJsonQuery : public TViewerPipeClient { JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), false); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); Timeout = FromStringWithDefault(params.Get("timeout"), 60000); - Query = params.Get("query"); - Database = params.Get("database"); - Stats = params.Get("stats"); - Action = params.Get("action"); - TString schemaStr = params.Get("schema"); - Schema = StringToSchemaType(schemaStr); - Syntax = params.Get("syntax"); - QueryId = params.Get("query_id"); - TransactionMode = params.Get("transaction_mode"); + if (params.Has("query")) { + Query = params.Get("query"); + } + if (params.Has("database")) { + Database = params.Get("database"); + } + if (params.Has("stats")) { + Stats = params.Get("stats"); + } + if (params.Has("action")) { + Action = params.Get("action"); + } + if (params.Has("schema")) { + Schema = StringToSchemaType(params.Get("schema")); + } + if (params.Has("syntax")) { + Syntax = params.Get("syntax"); + } + if (params.Has("query_id")) { + QueryId = params.Get("query_id"); + } + if (params.Has("transaction_mode")) { + TransactionMode = params.Get("transaction_mode"); + } + if (params.Has("base64")) { + IsBase64Encode = FromStringWithDefault(params.Get("base64"), true); + } + if (params.Has("limit_rows")) { + LimitRows = std::clamp(FromStringWithDefault(params.Get("limit_rows"), 10000), 1, 100000); + } Direct = FromStringWithDefault(params.Get("direct"), Direct); - IsBase64Encode = FromStringWithDefault(params.Get("base64"), true); } - bool ParsePostContent(const TStringBuf& content) { + bool ParsePostContent(TStringBuf content) { static NJson::TJsonReaderConfig JsonConfig; NJson::TJsonValue requestData; bool success = NJson::ReadJsonTree(content, &JsonConfig, &requestData); if (success) { - Query = Query.empty() ? requestData["query"].GetStringSafe({}) : Query; - Database = Database.empty() ? requestData["database"].GetStringSafe({}) : Database; - Stats = Stats.empty() ? requestData["stats"].GetStringSafe({}) : Stats; - Action = Action.empty() ? requestData["action"].GetStringSafe({}) : Action; - Syntax = Syntax.empty() ? requestData["syntax"].GetStringSafe({}) : Syntax; - QueryId = QueryId.empty() ? requestData["query_id"].GetStringSafe({}) : QueryId; - TransactionMode = TransactionMode.empty() ? requestData["transaction_mode"].GetStringSafe({}) : TransactionMode; + if (requestData.Has("query")) { + Query = requestData["query"].GetStringRobust(); + } + if (requestData.Has("database")) { + Database = requestData["database"].GetStringRobust(); + } + if (requestData.Has("stats")) { + Stats = requestData["stats"].GetStringRobust(); + } + if (requestData.Has("action")) { + Action = requestData["action"].GetStringRobust(); + } + if (requestData.Has("schema")) { + Schema = StringToSchemaType(requestData["schema"].GetStringRobust()); + } + if (requestData.Has("syntax")) { + Syntax = requestData["syntax"].GetStringRobust(); + } + if (requestData.Has("query_id")) { + QueryId = requestData["query_id"].GetStringRobust(); + } + if (requestData.Has("transaction_mode")) { + TransactionMode = requestData["transaction_mode"].GetStringRobust(); + } + if (requestData.Has("base64")) { + IsBase64Encode = requestData["base64"].GetBooleanRobust(); + } + if (requestData.Has("limit_rows")) { + LimitRows = std::clamp(requestData["limit_rows"].GetIntegerRobust(), 1, 100000); + } } return success; } @@ -112,9 +143,11 @@ class TJsonQuery : public TViewerPipeClient { { } - void Bootstrap() { + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } const auto& params(Event->Get()->Request.GetParams()); - InitConfig(params); ParseCgiParameters(params); if (IsPostContent()) { TStringBuf content = Event->Get()->Request.GetPostContent(); @@ -126,23 +159,10 @@ class TJsonQuery : public TViewerPipeClient { return TBase::ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "Query is empty"), "EmptyQuery"); } - Direct |= Event->Get()->Request.GetUri().StartsWith("/node/"); // we're already forwarding - Direct |= (Database == AppData()->TenantName); // we're already on the right node - - if (Database && !Direct) { - BLOG_TRACE("Requesting StateStorageEndpointsLookup for " << Database); - RequestStateStorageEndpointsLookup(Database); // to find some dynamic node and redirect query there - } else { - SendKpqProxyRequest(); - } + SendKpqProxyRequest(); Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); } - void HandleReply(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); - TBase::ReplyAndPassAway(MakeForward(GetNodesFromBoardReply(ev))); - } - void PassAway() override { if (QueryId) { Viewer->EndRunningQuery(QueryId, SelfId()); @@ -150,16 +170,16 @@ class TJsonQuery : public TViewerPipeClient { if (SessionId) { auto event = std::make_unique(); event->Record.MutableRequest()->SetSessionId(SessionId); - BLOG_TRACE("Closing session " << SessionId); Send(NKqp::MakeKqpProxyID(SelfId().NodeId()), event.release()); } TBase::PassAway(); - BLOG_TRACE("PassAway()"); + } + + void ReplyAndPassAway() override { } STATEFN(StateWork) { switch (ev->GetTypeRewrite()) { - hFunc(TEvStateStorage::TEvBoardInfo, HandleReply); hFunc(NKqp::TEvKqp::TEvCreateSessionResponse, HandleReply); hFunc(NKqp::TEvKqp::TEvQueryResponse, HandleReply); hFunc(NKqp::TEvKqp::TEvAbortExecution, HandleReply); @@ -198,7 +218,15 @@ class TJsonQuery : public TViewerPipeClient { Span.Attribute("database", Database); } } - BLOG_TRACE("Creating session"); + event->Record.SetApplicationName("ydb-ui"); + event->Record.SetClientAddress(Event->Get()->Request.GetRemoteAddr()); + event->Record.SetClientUserAgent(TString(Event->Get()->Request.GetHeader("User-Agent"))); + if (Event->Get()->UserToken) { + NACLibProto::TUserToken userToken; + if (userToken.ParseFromString(Event->Get()->UserToken)) { + event->Record.SetUserSID(userToken.GetUserSID()); + } + } CreateSessionResponse = MakeRequest(NKqp::MakeKqpProxyID(SelfId().NodeId()), event.release()); } @@ -228,7 +256,6 @@ class TJsonQuery : public TViewerPipeClient { TStringBuilder() << "Failed to create session, error " << ev->Get()->Record.GetYdbStatus()), "InternalError"); } SessionId = CreateSessionResponse->Record.GetResponse().GetSessionId(); - BLOG_TRACE("Session created " << SessionId); { auto event = std::make_unique(); @@ -247,11 +274,11 @@ class TJsonQuery : public TViewerPipeClient { if (Event->Get()->UserToken) { event->Record.SetUserToken(Event->Get()->UserToken); } - if (Action.empty() || Action == "execute-script" || Action == "execute") { + if (Action == "execute-script") { request.SetAction(NKikimrKqp::QUERY_ACTION_EXECUTE); request.SetType(NKikimrKqp::QUERY_TYPE_SQL_SCRIPT); request.SetKeepSession(false); - } else if (Action == "execute-query") { + } else if (Action.empty() || Action == "execute-query" || Action == "execute") { request.SetAction(NKikimrKqp::QUERY_ACTION_EXECUTE); request.SetType(NKikimrKqp::QUERY_TYPE_SQL_GENERIC_QUERY); request.SetKeepSession(false); @@ -283,7 +310,13 @@ class TJsonQuery : public TViewerPipeClient { request.SetAction(NKikimrKqp::QUERY_ACTION_EXPLAIN); request.SetType(NKikimrKqp::QUERY_TYPE_SQL_SCRIPT); } - if (Stats == "profile") { + if (Stats == "none") { + request.SetStatsMode(NYql::NDqProto::DQ_STATS_MODE_NONE); + request.SetCollectStats(Ydb::Table::QueryStatsCollection::STATS_COLLECTION_NONE); + } else if (Stats == "basic") { + request.SetStatsMode(NYql::NDqProto::DQ_STATS_MODE_BASIC); + request.SetCollectStats(Ydb::Table::QueryStatsCollection::STATS_COLLECTION_BASIC); + } else if (Stats == "profile") { request.SetStatsMode(NYql::NDqProto::DQ_STATS_MODE_PROFILE); request.SetCollectStats(Ydb::Table::QueryStatsCollection::STATS_COLLECTION_PROFILE); } else if (Stats == "full") { @@ -297,7 +330,6 @@ class TJsonQuery : public TViewerPipeClient { } ActorIdToProto(SelfId(), event->Record.MutableRequestActorId()); QueryResponse = MakeRequest(NKqp::MakeKqpProxyID(SelfId().NodeId()), event.Release()); - BLOG_TRACE("Query sent"); } private: @@ -336,7 +368,7 @@ class TJsonQuery : public TViewerPipeClient { case NYdb::EPrimitiveType::Interval: return TStringBuilder() << valueParser.GetInterval(); case NYdb::EPrimitiveType::Date32: - return valueParser.GetInt32(); + return valueParser.GetDate32(); case NYdb::EPrimitiveType::Datetime64: return valueParser.GetDatetime64(); case NYdb::EPrimitiveType::Timestamp64: @@ -391,14 +423,17 @@ class TJsonQuery : public TViewerPipeClient { case NYdb::TTypeParser::ETypeKind::Pg: return valueParser.GetPg().Content_; + case NYdb::TTypeParser::ETypeKind::Decimal: + return valueParser.GetDecimal().ToString(); + default: return NJson::JSON_UNDEFINED; } } void HandleReply(NKqp::TEvKqp::TEvQueryResponse::TPtr& ev) { - BLOG_TRACE("Query response received"); NJson::TJsonValue jsonResponse; + jsonResponse["version"] = Viewer->GetCapabilityVersion("/viewer/query"); if (ev->Get()->Record.GetRef().GetYdbStatus() == Ydb::StatusIds::SUCCESS) { QueryResponse.Set(std::move(ev)); MakeOkReply(jsonResponse, QueryResponse->Record.GetRef()); @@ -455,13 +490,27 @@ class TJsonQuery : public TViewerPipeClient { } void HandleReply(NKqp::TEvKqpExecuter::TEvStreamData::TPtr& ev) { - const NKikimrKqp::TEvExecuterStreamData& data(ev->Get()->Record); - - ResultSets.emplace_back(); - ResultSets.back() = std::move(data.GetResultSet()); + QueryResponse.Event("StreamData"); + NKikimrKqp::TEvExecuterStreamData& data(ev->Get()->Record); + + if (TotalRows < LimitRows) { + int rowsAvailable = LimitRows - TotalRows; + if (data.GetResultSet().rows_size() > rowsAvailable) { + data.MutableResultSet()->mutable_rows()->Truncate(rowsAvailable); + data.MutableResultSet()->set_truncated(true); + } + TotalRows += data.GetResultSet().rows_size(); + if (ResultSets.size() <= data.GetQueryResultIndex()) { + ResultSets.resize(data.GetQueryResultIndex() + 1); + } + ResultSets[data.GetQueryResultIndex()].emplace_back() = std::move(*data.MutableResultSet()); + } THolder ack = MakeHolder(); ack->Record.SetSeqNo(ev->Get()->Record.GetSeqNo()); + if (TotalRows >= LimitRows) { + ack->Record.SetEnough(true); + } Send(ev->Sender, ack.Release()); } @@ -471,7 +520,6 @@ class TJsonQuery : public TViewerPipeClient { if (SessionId) { auto event = std::make_unique(); event->Record.MutableRequest()->SetSessionId(SessionId); - BLOG_TRACE("Cancelling query in session " << SessionId); Send(NKqp::MakeKqpProxyID(SelfId().NodeId()), event.release()); error << ", query was cancelled"; } @@ -503,11 +551,11 @@ class TJsonQuery : public TViewerPipeClient { for (const auto& result : response.GetResults()) { Ydb::ResultSet resultSet; NKqp::ConvertKqpQueryResultToDbResult(result, &resultSet); - ResultSets.emplace_back(std::move(resultSet)); + ResultSets.emplace_back().emplace_back(std::move(resultSet)); } for (const auto& result : response.GetYdbResults()) { - ResultSets.emplace_back(result); + ResultSets.emplace_back().emplace_back(result); } } catch (const std::exception& ex) { @@ -522,15 +570,16 @@ class TJsonQuery : public TViewerPipeClient { if (Schema == ESchemaType::Classic) { NJson::TJsonValue& jsonResults = jsonResponse["result"]; jsonResults.SetType(NJson::JSON_ARRAY); - for (auto it = ResultSets.begin(); it != ResultSets.end(); ++it) { - NYdb::TResultSet resultSet(*it); - const auto& columnsMeta = resultSet.GetColumnsMeta(); - NYdb::TResultSetParser rsParser(resultSet); - while (rsParser.TryNextRow()) { - NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); - for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { - const NYdb::TColumn& columnMeta = columnsMeta[columnNum]; - jsonRow[columnMeta.Name] = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + for (const auto& resultSets : ResultSets) { + for (NYdb::TResultSet resultSet : resultSets) { + const auto& columnsMeta = resultSet.GetColumnsMeta(); + NYdb::TResultSetParser rsParser(resultSet); + while (rsParser.TryNextRow()) { + NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); + for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { + const NYdb::TColumn& columnMeta = columnsMeta[columnNum]; + jsonRow[columnMeta.Name] = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + } } } } @@ -539,7 +588,7 @@ class TJsonQuery : public TViewerPipeClient { if (Schema == ESchemaType::Modern) { { NJson::TJsonValue& jsonColumns = jsonResponse["columns"]; - NYdb::TResultSet resultSet(ResultSets.front()); + NYdb::TResultSet resultSet(ResultSets.front().front()); const auto& columnsMeta = resultSet.GetColumnsMeta(); jsonColumns.SetType(NJson::JSON_ARRAY); for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { @@ -552,16 +601,17 @@ class TJsonQuery : public TViewerPipeClient { NJson::TJsonValue& jsonResults = jsonResponse["result"]; jsonResults.SetType(NJson::JSON_ARRAY); - for (auto it = ResultSets.begin(); it != ResultSets.end(); ++it) { - NYdb::TResultSet resultSet(*it); - const auto& columnsMeta = resultSet.GetColumnsMeta(); - NYdb::TResultSetParser rsParser(resultSet); - while (rsParser.TryNextRow()) { - NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); - jsonRow.SetType(NJson::JSON_ARRAY); - for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { - NJson::TJsonValue& jsonColumn = jsonRow.AppendValue({}); - jsonColumn = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + for (const auto& resultSets : ResultSets) { + for (NYdb::TResultSet resultSet : resultSets) { + const auto& columnsMeta = resultSet.GetColumnsMeta(); + NYdb::TResultSetParser rsParser(resultSet); + while (rsParser.TryNextRow()) { + NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); + jsonRow.SetType(NJson::JSON_ARRAY); + for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { + NJson::TJsonValue& jsonColumn = jsonRow.AppendValue({}); + jsonColumn = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + } } } } @@ -570,28 +620,36 @@ class TJsonQuery : public TViewerPipeClient { if (Schema == ESchemaType::Multi) { NJson::TJsonValue& jsonResults = jsonResponse["result"]; jsonResults.SetType(NJson::JSON_ARRAY); - for (auto it = ResultSets.begin(); it != ResultSets.end(); ++it) { - NYdb::TResultSet resultSet(*it); - const auto& columnsMeta = resultSet.GetColumnsMeta(); + for (const auto& resultSets : ResultSets) { NJson::TJsonValue& jsonResult = jsonResults.AppendValue({}); + bool hasColumns = false; + for (NYdb::TResultSet resultSet : resultSets) { + if (!hasColumns) { + NJson::TJsonValue& jsonColumns = jsonResult["columns"]; + jsonColumns.SetType(NJson::JSON_ARRAY); + const auto& columnsMeta = resultSet.GetColumnsMeta(); + for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { + NJson::TJsonValue& jsonColumn = jsonColumns.AppendValue({}); + const NYdb::TColumn& columnMeta = columnsMeta[columnNum]; + jsonColumn["name"] = columnMeta.Name; + jsonColumn["type"] = columnMeta.Type.ToString(); + } + hasColumns = true; + } - NJson::TJsonValue& jsonColumns = jsonResult["columns"]; - jsonColumns.SetType(NJson::JSON_ARRAY); - for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { - NJson::TJsonValue& jsonColumn = jsonColumns.AppendValue({}); - const NYdb::TColumn& columnMeta = columnsMeta[columnNum]; - jsonColumn["name"] = columnMeta.Name; - jsonColumn["type"] = columnMeta.Type.ToString(); - } + NJson::TJsonValue& jsonRows = jsonResult["rows"]; + NYdb::TResultSetParser rsParser(resultSet); + while (rsParser.TryNextRow()) { + NJson::TJsonValue& jsonRow = jsonRows.AppendValue({}); + jsonRow.SetType(NJson::JSON_ARRAY); + for (size_t columnNum = 0; columnNum < rsParser.ColumnsCount(); ++columnNum) { + NJson::TJsonValue& jsonColumn = jsonRow.AppendValue({}); + jsonColumn = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + } + } - NJson::TJsonValue& jsonRows = jsonResult["rows"]; - NYdb::TResultSetParser rsParser(resultSet); - while (rsParser.TryNextRow()) { - NJson::TJsonValue& jsonRow = jsonRows.AppendValue({}); - jsonRow.SetType(NJson::JSON_ARRAY); - for (size_t columnNum = 0; columnNum < columnsMeta.size(); ++columnNum) { - NJson::TJsonValue& jsonColumn = jsonRow.AppendValue({}); - jsonColumn = ColumnValueToJsonValue(rsParser.ColumnParser(columnNum)); + if (resultSet.Truncated()) { + jsonResult["truncated"] = true; } } } @@ -600,14 +658,15 @@ class TJsonQuery : public TViewerPipeClient { if (Schema == ESchemaType::Ydb) { NJson::TJsonValue& jsonResults = jsonResponse["result"]; jsonResults.SetType(NJson::JSON_ARRAY); - for (auto it = ResultSets.begin(); it != ResultSets.end(); ++it) { - NYdb::TResultSet resultSet(*it); - const auto& columnsMeta = resultSet.GetColumnsMeta(); - NYdb::TResultSetParser rsParser(resultSet); - while (rsParser.TryNextRow()) { - NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); - TString row = NYdb::FormatResultRowJson(rsParser, columnsMeta, IsBase64Encode ? NYdb::EBinaryStringEncoding::Base64 : NYdb::EBinaryStringEncoding::Unicode); - NJson::ReadJsonTree(row, &jsonRow); + for (const auto& resultSets : ResultSets) { + for (NYdb::TResultSet resultSet : resultSets) { + const auto& columnsMeta = resultSet.GetColumnsMeta(); + NYdb::TResultSetParser rsParser(resultSet); + while (rsParser.TryNextRow()) { + NJson::TJsonValue& jsonRow = jsonResults.AppendValue({}); + TString row = NYdb::FormatResultRowJson(rsParser, columnsMeta, IsBase64Encode ? NYdb::EBinaryStringEncoding::Base64 : NYdb::EBinaryStringEncoding::Unicode); + NJson::ReadJsonTree(row, &jsonRow); + } } } } @@ -622,131 +681,131 @@ class TJsonQuery : public TViewerPipeClient { NProtobufJson::Proto2Json(response.GetQueryStats(), jsonResponse["stats"]); } } -}; -template <> -YAML::Node TJsonRequestSwagger::GetSwagger() { - YAML::Node node = YAML::Load(R"___( +public: + static YAML::Node GetSwagger() { + YAML::Node node = YAML::Load(R"___( post: - tags: - - viewer - summary: Executes SQL query - description: Executes SQL query - parameters: - - name: action - in: query - type: string - enum: [execute-scan, execute-script, execute-query, execute-data, explain-ast, explain-scan, explain-script, explain-query, explain-data, cancel-query] - required: true - description: > - execute method: - * `execute-query` - execute query (QueryService) - * `execute-data` - execute data query (DataQuery) - * `execute-scan` - execute scan query (ScanQuery) - * `execute-script` - execute script query (ScriptingService) - * `explain-query` - explain query (QueryService) - * `explain-data` - explain data query (DataQuery) - * `explain-scan` - explain scan query (ScanQuery) - * `explain-script` - explain script query (ScriptingService) - * `cancel-query` - cancel query (using query_id) - - name: database - in: query - description: database name - type: string - required: false - - name: query - in: query - description: SQL query text - type: string - required: false - - name: query_id - in: query - description: unique query identifier (uuid) - use the same id to cancel query - required: false - - name: syntax - in: query - description: > - query syntax: - * `yql_v1` - YQL v1 (default) - * `pg` - PostgreSQL compatible - type: string - enum: [yql_v1, pg] - required: false - - name: schema - in: query - description: > - result format schema: - * `classic` - * `modern` - * `multi` - * `ydb` - type: string - enum: [classic, modern, ydb, multi] - required: false - - name: stats - in: query - description: > - return stats: - * `profile` - * `full` - type: string - enum: [profile, full] - required: false - - name: transaction_mode - in: query - description: > - transaction mode: - * `serializable-read-write` - * `online-read-only` - * `stale-read-only` - * `snapshot-read-only` - type: string - enum: [serializable-read-write, online-read-only, stale-read-only, snapshot-read-only] - required: false - - name: direct - in: query - description: force processing query on current node - type: boolean - required: false - - name: base64 - in: query - description: return strings using base64 encoding - type: string - required: false - - name: timeout - in: query - description: timeout in ms - type: integer - required: false - - name: ui64 - in: query - description: return ui64 as number to avoid 56-bit js rounding - type: boolean - required: false - requestBody: + tags: + - viewer + summary: Executes SQL query description: Executes SQL query - required: false - content: - application/json: - schema: - type: object - description: the same properties as in query parameters - responses: - 200: - description: OK - content: - application/json: - schema: - type: object - description: format depends on schema parameter - 400: - description: Bad Request - 403: - description: Forbidden - 504: - description: Gateway Timeout - )___"); - return node; -} -} + parameters: + - name: action + in: query + type: string + enum: [execute-scan, execute-script, execute-query, execute-data, explain-ast, explain-scan, explain-script, explain-query, explain-data, cancel-query] + required: true + description: > + execute method: + * `execute-query` - execute query (QueryService) + * `execute-data` - execute data query (DataQuery) + * `execute-scan` - execute scan query (ScanQuery) + * `execute-script` - execute script query (ScriptingService) + * `explain-query` - explain query (QueryService) + * `explain-data` - explain data query (DataQuery) + * `explain-scan` - explain scan query (ScanQuery) + * `explain-script` - explain script query (ScriptingService) + * `cancel-query` - cancel query (using query_id) + - name: database + in: query + description: database name + type: string + required: false + - name: query + in: query + description: SQL query text + type: string + required: false + - name: query_id + in: query + description: unique query identifier (uuid) - use the same id to cancel query + required: false + - name: syntax + in: query + description: > + query syntax: + * `yql_v1` - YQL v1 (default) + * `pg` - PostgreSQL compatible + type: string + enum: [yql_v1, pg] + required: false + - name: schema + in: query + description: > + result format schema: + * `classic` + * `modern` + * `multi` + * `ydb` + type: string + enum: [classic, modern, ydb, multi] + required: false + - name: stats + in: query + description: > + return stats: + * `profile` + * `full` + type: string + enum: [profile, full] + required: false + - name: transaction_mode + in: query + description: > + transaction mode: + * `serializable-read-write` + * `online-read-only` + * `stale-read-only` + * `snapshot-read-only` + type: string + enum: [serializable-read-write, online-read-only, stale-read-only, snapshot-read-only] + required: false + - name: direct + in: query + description: force processing query on current node + type: boolean + required: false + - name: base64 + in: query + description: return strings using base64 encoding + type: string + required: false + - name: timeout + in: query + description: timeout in ms + type: integer + required: false + - name: ui64 + in: query + description: return ui64 as number to avoid 56-bit js rounding + type: boolean + required: false + requestBody: + description: Executes SQL query + required: false + content: + application/json: + schema: + type: object + description: the same properties as in query parameters + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + description: format depends on schema parameter + 400: + description: Bad Request + 403: + description: Forbidden + 504: + description: Gateway Timeout + )___"); + return node; + } +}; + } diff --git a/ydb/core/viewer/json_query_old.h b/ydb/core/viewer/viewer_query_old.h similarity index 97% rename from ydb/core/viewer/json_query_old.h rename to ydb/core/viewer/viewer_query_old.h index 8a532385ce65..9b130e9be793 100644 --- a/ydb/core/viewer/json_query_old.h +++ b/ydb/core/viewer/viewer_query_old.h @@ -1,23 +1,14 @@ #pragma once +#include "json_pipe_req.h" +#include "log.h" #include "viewer.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "viewer_request.h" #include #include -#include #include #include -#include "json_pipe_req.h" -#include "viewer_request.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NMonitoring; @@ -25,9 +16,9 @@ using ::google::protobuf::FieldDescriptor; // we only keep this class for compatibility with viewer requests // DO NOT EDIT THIS FILE - it should be deleted after 2025-01-01 -class TJsonQueryOld : public TViewerPipeClient { +class TJsonQueryOld : public TViewerPipeClient { using TThis = TJsonQueryOld; - using TBase = TViewerPipeClient; + using TBase = TViewerPipeClient; IViewer* Viewer; TJsonSettings JsonSettings; NMon::TEvHttpInfo::TPtr Event; @@ -56,10 +47,6 @@ class TJsonQueryOld : public TViewerPipeClient { bool MadeKqpProxyRequest = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - ESchemaType StringToSchemaType(const TString& schemaStr) { if (schemaStr == "classic") { return ESchemaType::Classic; @@ -227,7 +214,7 @@ class TJsonQueryOld : public TViewerPipeClient { Send(NKqp::MakeKqpProxyID(SelfId().NodeId()), event.Release()); } - void Bootstrap() { + void Bootstrap() override { if (Query.empty()) { if (Event) { ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), {}, "Bad Request")); @@ -478,6 +465,9 @@ class TJsonQueryOld : public TViewerPipeClient { PassAway(); } + void ReplyAndPassAway() override { + } + private: void MakeErrorReply(NJson::TJsonValue& jsonResponse, NKikimrKqp::TEvQueryResponse& record) { NJson::TJsonValue& jsonIssues = jsonResponse["issues"]; @@ -628,9 +618,6 @@ class TJsonQueryOld : public TViewerPipeClient { NProtobufJson::Proto2Json(response.GetQueryStats(), jsonResponse["stats"]); } } - }; - -} } diff --git a/ydb/core/viewer/json_render.h b/ydb/core/viewer/viewer_render.h similarity index 52% rename from ydb/core/viewer/json_render.h rename to ydb/core/viewer/viewer_render.h index a0788879a19c..44aea71caff0 100644 --- a/ydb/core/viewer/json_render.h +++ b/ydb/core/viewer/viewer_render.h @@ -1,49 +1,31 @@ #pragma once -#include -#include -#include -#include -#include +#include "json_handlers.h" #include "json_pipe_req.h" -#include "viewer_request.h" -#include "viewer.h" #include "log.h" +#include "viewer_request.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NMonitoring; -class TJsonRender : public TViewerPipeClient { +class TJsonRender : public TViewerPipeClient { using TThis = TJsonRender; - using TBase = TViewerPipeClient; - IViewer* Viewer; - NMon::TEvHttpInfo::TPtr Event; + using TBase = TViewerPipeClient; TEvViewer::TEvViewerRequest::TPtr ViewerRequest; ui32 Timeout = 0; std::vector Metrics; - TString Database; TCgiParameters Params; - std::optional SubscribedNodeId; - std::vector TenantDynamicNodes; - bool Direct = false; - bool MadeProxyRequest = false; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - - TJsonRender(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) - : Viewer(viewer) - , Event(ev) + TJsonRender(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TViewerPipeClient(viewer, ev) { const auto& params(Event->Get()->Request.GetParams()); InitConfig(params); - Database = params.Get("database"); - Direct = FromStringWithDefault(params.Get("direct"), Direct); Timeout = FromStringWithDefault(params.Get("timeout"), 30000); } @@ -58,7 +40,10 @@ class TJsonRender : public TViewerPipeClient { Timeout = ViewerRequest->Get()->Record.GetTimeout(); } - void Bootstrap() { + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } auto postData = Event ? Event->Get()->Request.GetPostContent() : ViewerRequest->Get()->Record.GetRenderRequest().GetContent(); @@ -78,14 +63,7 @@ class TJsonRender : public TViewerPipeClient { ++num; } } - //StringSplitter(Params.Get("target")).Split(',').SkipEmpty().Collect(&Metrics); - - if (Database && !Direct) { - RequestStateStorageEndpointsLookup(Database); // to find some dynamic node and redirect there - } - if (Requests == 0) { - SendGraphRequest(); - } + SendGraphRequest(); } else { ReplyAndPassAway(Viewer->GetHTTPBADREQUEST(Event->Get(), {}, "Bad Request")); return; @@ -94,80 +72,14 @@ class TJsonRender : public TViewerPipeClient { Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); } - void PassAway() override { - if (SubscribedNodeId.has_value()) { - Send(TActivationContext::InterconnectProxy(SubscribedNodeId.value()), new TEvents::TEvUnsubscribe()); - } - TBase::PassAway(); - BLOG_TRACE("PassAway()"); - } - STATEFN(StateWork) { switch (ev->GetTypeRewrite()) { - hFunc(TEvStateStorage::TEvBoardInfo, Handle); - hFunc(TEvents::TEvUndelivered, Undelivered); - hFunc(TEvInterconnect::TEvNodeConnected, Connected); - hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); - hFunc(TEvViewer::TEvViewerResponse, Handle); hFunc(NGraph::TEvGraph::TEvMetricsResult, Handle); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } - void Connected(TEvInterconnect::TEvNodeConnected::TPtr &) {} - - void Undelivered(TEvents::TEvUndelivered::TPtr &ev) { - if (ev->Get()->SourceType == NViewer::TEvViewer::EvViewerRequest) { - SendGraphRequest(); - } - } - - void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr &) { - SendGraphRequest(); - } - - void SendDynamicNodeRenderRequest() { - ui64 hash = std::hash()(Event->Get()->Request.GetRemoteAddr()); - - auto itPos = std::next(TenantDynamicNodes.begin(), hash % TenantDynamicNodes.size()); - std::nth_element(TenantDynamicNodes.begin(), itPos, TenantDynamicNodes.end()); - - TNodeId nodeId = *itPos; - SubscribedNodeId = nodeId; - TActorId viewerServiceId = MakeViewerID(nodeId); - - THolder request = MakeHolder(); - request->Record.SetTimeout(Timeout); - auto renderRequest = request->Record.MutableRenderRequest(); - renderRequest->SetUri(TString(Event->Get()->Request.GetUri())); - - TStringBuf content = Event->Get()->Request.GetPostContent(); - renderRequest->SetContent(TString(content)); - - ViewerWhiteboardCookie cookie(NKikimrViewer::TEvViewerRequest::kRenderRequest, nodeId); - SendRequest(viewerServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, cookie.ToUi64()); - } - - void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { - BLOG_TRACE("Received TEvBoardInfo"); - if (ev->Get()->Status == TEvStateStorage::TEvBoardInfo::EStatus::Ok) { - for (const auto& [actorId, infoEntry] : ev->Get()->InfoEntries) { - TenantDynamicNodes.emplace_back(actorId.NodeId()); - } - } - if (TenantDynamicNodes.empty()) { - SendGraphRequest(); - } else { - SendDynamicNodeRenderRequest(); - } - } - void SendGraphRequest() { - if (MadeProxyRequest) { - return; - } - MadeProxyRequest = true; NKikimrGraph::TEvGetMetrics getRequest; if (Metrics.size() > 0) { for (const auto& metric : Metrics) { @@ -200,22 +112,19 @@ class TJsonRender : public TViewerPipeClient { if (response.GetError()) { json["status"] = "error"; json["error"] = response.GetError(); - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false))); - return; + return ReplyAndPassAway(GetHTTPOKJSON(json)); } if (response.DataSize() != Metrics.size()) { json["status"] = "error"; json["error"] = "Invalid data size received"; - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false))); - return; + return ReplyAndPassAway(GetHTTPOKJSON(json)); } for (size_t nMetric = 0; nMetric < response.DataSize(); ++nMetric) { const auto& protoMetric(response.GetData(nMetric)); if (response.TimeSize() != protoMetric.ValuesSize()) { json["status"] = "error"; json["error"] = "Invalid value size received"; - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false))); - return; + return ReplyAndPassAway(GetHTTPOKJSON(json)); } } { // graphite @@ -241,7 +150,7 @@ class TJsonRender : public TViewerPipeClient { } } - ReplyAndPassAway(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json, false))); + ReplyAndPassAway(GetHTTPOKJSON(json)); } else { TEvViewer::TEvViewerResponse* viewerResponse = new TEvViewer::TEvViewerResponse(); viewerResponse->Record.MutableRenderResponse()->CopyFrom(response); @@ -253,15 +162,6 @@ class TJsonRender : public TViewerPipeClient { HandleRenderResponse(ev->Get()->Record); } - void Handle(TEvViewer::TEvViewerResponse::TPtr& ev) { - auto& record = ev.Get()->Get()->Record; - if (record.HasRenderResponse()) { - HandleRenderResponse(*(record.MutableRenderResponse())); - } else { - SendGraphRequest(); // fallback - } - } - void HandleTimeout() { if (Event) { ReplyAndPassAway(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get())); @@ -281,64 +181,55 @@ class TJsonRender : public TViewerPipeClient { Send(Event->Sender, new NMon::TEvHttpInfoRes(std::move(data), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: target - in: query - description: metrics comma delimited - required: true - type: string - - name: from - in: query - description: time in seconds - required: false - type: integer - - name: database - in: query - description: database name - required: false - type: string - - name: direct - in: query - description: force processing query on current node - required: false - type: boolean - - name: until - in: query - description: time in seconds - required: false - type: integer - - name: maxDataPoints - in: query - description: maximum number of data points - required: false - type: integer - - name: format - in: query - description: response format - required: false - type: string - )___"); + void ReplyAndPassAway() override { + } + + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Graph data", + .Description = "Returns graph data in graphite format", + }); + yaml.AddParameter({ + .Name = "target", + .Description = "metrics comma delimited", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "from", + .Description = "time in seconds", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "database", + .Description = "database name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "direct", + .Description = "force processing query on current node", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "until", + .Description = "time in seconds", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "maxDataPoints", + .Description = "maximum number of data points", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "format", + .Description = "response format", + .Type = "string", + }); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Graph data"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns graph data in graphite format"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_request.cpp b/ydb/core/viewer/viewer_request.cpp index 0977f26a9df3..24f4a42d8d32 100644 --- a/ydb/core/viewer/viewer_request.cpp +++ b/ydb/core/viewer/viewer_request.cpp @@ -1,25 +1,24 @@ -#include - #include "viewer_request.h" +#include "viewer_autocomplete.h" +#include "viewer_query_old.h" +#include "viewer_render.h" +#include "viewer_sysinfo.h" +#include "viewer_tabletinfo.h" +#include "viewer_vdiskinfo.h" +#include "viewer_pdiskinfo.h" +#include "viewer_bsgroupinfo.h" #include "wb_req.h" -#include "json_tabletinfo.h" -#include "json_sysinfo.h" -#include "json_query_old.h" -#include "json_render.h" -#include "json_autocomplete.h" - -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; template -class TViewerWhiteboardRequest : public TWhiteboardRequest, TRequestEventType, TResponseEventType> { +class TViewerWhiteboardRequest : public TWhiteboardRequest { protected: using TThis = TViewerWhiteboardRequest; - using TBase = TWhiteboardRequest; + using TBase = TWhiteboardRequest; using TResponseType = typename TResponseEventType::ProtoRecordType; IViewer* Viewer; TEvViewer::TEvViewerRequest::TPtr Event; @@ -31,12 +30,13 @@ class TViewerWhiteboardRequest : public TWhiteboardRequestTraceId)) + , Event(ev) { } void Bootstrap() override { - TBase::RequestSettings.MergeFields = TWhiteboardInfo::GetDefaultMergeField(); + TBase::RequestSettings.MergeFields = Event->Get()->Record.GetMergeFields(); TBase::RequestSettings.Timeout = Event->Get()->Record.GetTimeout(); for (TNodeId nodeId : Event->Get()->Record.GetLocation().GetNodeId()) { TBase::RequestSettings.FilterNodeIds.push_back(nodeId); @@ -44,24 +44,56 @@ class TViewerWhiteboardRequest : public TWhiteboardRequest void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields); + THolder BuildRequest() override; + + template + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields); - template<> void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { + template<> + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { NKikimr::NViewer::MergeWhiteboardResponses(*(response->Record.MutableTabletResponse()), perNodeStateInfo, fields); } - template<> void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { + template<> + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { NKikimr::NViewer::MergeWhiteboardResponses(*(response->Record.MutableSystemResponse()), perNodeStateInfo, fields); } - void ReplyAndPassAway() { + template<> + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { + NKikimr::NViewer::MergeWhiteboardResponses(*(response->Record.MutableVDiskResponse()), perNodeStateInfo, fields); + } + + template<> + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { + NKikimr::NViewer::MergeWhiteboardResponses(*(response->Record.MutablePDiskResponse()), perNodeStateInfo, fields); + } + + template<> + void MergeWhiteboardResponses(TEvViewer::TEvViewerResponse* response, TMap& perNodeStateInfo, const TString& fields) { + NKikimr::NViewer::MergeWhiteboardResponses(*(response->Record.MutableBSGroupResponse()), perNodeStateInfo, fields); + } + + static void Merge(NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, TResponseType& nodeResponse); + + void ReplyAndPassAway() override { auto response = MakeHolder(); auto& locationResponded = (*response->Record.MutableLocationResponded()); - for (const auto& [nodeId, nodeResponse] : TBase::PerNodeStateInfo) { - locationResponded.AddNodeId(nodeId); - } - MergeWhiteboardResponses(response.Get(), TBase::PerNodeStateInfo, TBase::RequestSettings.MergeFields); // PerNodeStateInfo will be invalidated + if (TBase::RequestSettings.MergeFields) { + auto perNodeStateInfo = TBase::GetPerNodeStateInfo(); + for (const auto& [nodeId, nodeResponse] : perNodeStateInfo) { + locationResponded.AddNodeId(nodeId); + } + MergeWhiteboardResponses(response.Get(), perNodeStateInfo, TBase::RequestSettings.MergeFields); + } else { + for (auto& [nodeId, nodeResponse] : TBase::NodeResponses) { + if (nodeResponse.IsOk()) { + locationResponded.AddNodeId(nodeId); + Merge(response->Record, nodeId, nodeResponse.Get()->Record); + } + } + } TBase::Send(Event->Sender, response.Release(), 0, Event->Cookie); TBase::PassAway(); @@ -74,21 +106,114 @@ IActor* CreateViewerRequestHandler(TEvViewer::TEvViewerRequest::TPtr& request) { return new TViewerWhiteboardRequest(request); case NKikimrViewer::TEvViewerRequest::kSystemRequest: return new TViewerWhiteboardRequest(request); + case NKikimrViewer::TEvViewerRequest::kVDiskRequest: + return new TViewerWhiteboardRequest(request); + case NKikimrViewer::TEvViewerRequest::kPDiskRequest: + return new TViewerWhiteboardRequest(request); + case NKikimrViewer::TEvViewerRequest::kBSGroupRequest: + return new TViewerWhiteboardRequest(request); case NKikimrViewer::TEvViewerRequest::kQueryRequest: return new TJsonQueryOld(request); case NKikimrViewer::TEvViewerRequest::kRenderRequest: return new TJsonRender(request); case NKikimrViewer::TEvViewerRequest::kAutocompleteRequest: return new TJsonAutocomplete(request); - case NKikimrViewer::TEvViewerRequest::kReserved16: - case NKikimrViewer::TEvViewerRequest::kReserved17: - case NKikimrViewer::TEvViewerRequest::kReserved18: - case NKikimrViewer::TEvViewerRequest::REQUEST_NOT_SET: + default: return nullptr; } return nullptr; } +template<> +THolder TViewerWhiteboardRequest::BuildRequest() { + auto request = TBase::BuildRequest(); + request->Record.MergeFrom(Event->Get()->Record.GetTabletRequest()); + return request; +} + +template<> +void TViewerWhiteboardRequest::Merge( + NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, NKikimrWhiteboard::TEvTabletStateResponse& nodeResponse) { + auto& target = *viewerResponse.MutableTabletResponse(); + for (auto& info : *nodeResponse.MutableTabletStateInfo()) { + auto& i = *target.AddTabletStateInfo(); + i.MergeFrom(info); + i.SetNodeId(nodeId); + } +} + +template<> +THolder TViewerWhiteboardRequest::BuildRequest() { + auto request = TBase::BuildRequest(); + request->Record.MergeFrom(Event->Get()->Record.GetSystemRequest()); + return request; +} + +template<> +void TViewerWhiteboardRequest::Merge( + NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, NKikimrWhiteboard::TEvSystemStateResponse& nodeResponse) { + auto& target = *viewerResponse.MutableSystemResponse(); + for (auto& info : *nodeResponse.MutableSystemStateInfo()) { + auto& i = *target.AddSystemStateInfo(); + i.MergeFrom(info); + i.SetNodeId(nodeId); + } +} + +template<> +THolder TViewerWhiteboardRequest::BuildRequest() { + auto request = TBase::BuildRequest(); + request->Record.MergeFrom(Event->Get()->Record.GetVDiskRequest()); + return request; +} + +template<> +void TViewerWhiteboardRequest::Merge( + NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, NKikimrWhiteboard::TEvVDiskStateResponse& nodeResponse) { + auto& target = *viewerResponse.MutableVDiskResponse(); + for (auto& info : *nodeResponse.MutableVDiskStateInfo()) { + auto& i = *target.AddVDiskStateInfo(); + i.MergeFrom(info); + i.SetNodeId(nodeId); + } +} + +template<> +THolder TViewerWhiteboardRequest::BuildRequest() { + auto request = TBase::BuildRequest(); + request->Record.MergeFrom(Event->Get()->Record.GetPDiskRequest()); + return request; +} + +template<> +void TViewerWhiteboardRequest::Merge( + NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, NKikimrWhiteboard::TEvPDiskStateResponse& nodeResponse) { + auto& target = *viewerResponse.MutablePDiskResponse(); + for (auto& info : *nodeResponse.MutablePDiskStateInfo()) { + auto& i = *target.AddPDiskStateInfo(); + i.MergeFrom(info); + i.SetNodeId(nodeId); + } +} + +template<> +THolder TViewerWhiteboardRequest::BuildRequest() { + auto request = TBase::BuildRequest(); + request->Record.MergeFrom(Event->Get()->Record.GetBSGroupRequest()); + return request; +} + +template<> +void TViewerWhiteboardRequest::Merge( + NKikimrViewer::TEvViewerResponse& viewerResponse, TNodeId nodeId, NKikimrWhiteboard::TEvBSGroupStateResponse& nodeResponse) { + auto& target = *viewerResponse.MutableBSGroupResponse(); + for (auto& info : *nodeResponse.MutableBSGroupStateInfo()) { + auto& i = *target.AddBSGroupStateInfo(); + i.MergeFrom(info); + i.SetNodeId(nodeId); + } +} + bool IsPostContent(const NMon::TEvHttpInfo::TPtr& event) { if (event->Get()->Request.GetMethod() == HTTP_METHOD_POST) { const THttpHeaders& headers = event->Get()->Request.GetHeaders(); @@ -107,4 +232,3 @@ bool IsPostContent(const NMon::TEvHttpInfo::TPtr& event) { } } -} diff --git a/ydb/core/viewer/viewer_request.h b/ydb/core/viewer/viewer_request.h index 6516dbb59d18..20fb126761fb 100644 --- a/ydb/core/viewer/viewer_request.h +++ b/ydb/core/viewer/viewer_request.h @@ -1,9 +1,7 @@ #pragma once - #include "viewer.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -41,4 +39,3 @@ IActor* CreateViewerRequestHandler(TEvViewer::TEvViewerRequest::TPtr& request); bool IsPostContent(const NMon::TEvHttpInfo::TPtr& event); } -} diff --git a/ydb/core/viewer/json_storage.h b/ydb/core/viewer/viewer_storage.h similarity index 85% rename from ydb/core/viewer/json_storage.h rename to ydb/core/viewer/viewer_storage.h index fba5047a69e0..dc23e974e84c 100644 --- a/ydb/core/viewer/json_storage.h +++ b/ydb/core/viewer/viewer_storage.h @@ -1,8 +1,10 @@ #pragma once +#include "json_handlers.h" #include "json_storage_base.h" +#include "viewer_pdiskinfo.h" +#include "viewer_vdiskinfo.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; @@ -478,124 +480,105 @@ class TJsonStorage : public TJsonStorageBase { Send(Initiator, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), std::move(json.Str())), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: tenant - in: query - description: tenant name - required: false - type: string - - name: pool - in: query - description: storage pool name - required: false - type: string - - name: node_id - in: query - description: node id - required: false - type: integer - - name: pdisk_id - in: query - description: pdisk id - required: false - type: integer - - name: group_id - in: query - description: group id - required: false - type: integer - - name: need_groups - in: query - description: return groups information - required: false - type: boolean - default: true - - name: need_disks - in: query - description: return disks information - required: false - type: boolean - default: true - - name: with - in: query - description: filter groups by missing or space - required: false - type: string - - name: version - in: query - description: query version (v1, v2) - required: false - type: string - - name: usage_pace - in: query - description: bucket size as a percentage - required: false - type: integer - default: 5 - - name: usage_buckets - in: query - description: filter groups by usage buckets - required: false - type: integer - - name: sort - in: query - description: sort by (PoolName,Kind,MediaType,Erasure,Degraded,Usage,GroupId,Used,Limit,Read,Write) - required: false - type: string - - name: offset - in: query - description: skip N nodes - required: false - type: integer - - name: limit - in: query - description: limit to N nodes - required: false - type: integer - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Storage information", + .Description = "Returns information about storage" + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean" + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean" + }); + yaml.AddParameter({ + .Name = "tenant", + .Description = "tenant name", + .Type = "string" + }); + yaml.AddParameter({ + .Name = "pool", + .Description = "storage pool name", + .Type = "string" + }); + yaml.AddParameter({ + .Name = "node_id", + .Description = "node id", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "pdisk_id", + .Description = "pdisk id", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "group_id", + .Description = "group id", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "need_groups", + .Description = "return groups information", + .Type = "boolean", + .Default = "true" + }); + yaml.AddParameter({ + .Name = "need_disks", + .Description = "return disks information", + .Type = "boolean", + .Default = "true" + }); + yaml.AddParameter({ + .Name = "with", + .Description = "filter groups by missing or space", + .Type = "string" + }); + yaml.AddParameter({ + .Name = "version", + .Description = "query version (v1, v2)", + .Type = "string" + }); + yaml.AddParameter({ + .Name = "usage_pace", + .Description = "bucket size as a percentage", + .Type = "integer", + .Default = "5" + }); + yaml.AddParameter({ + .Name = "usage_buckets", + .Description = "filter groups by usage buckets", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "sort", + .Description = "sort by (PoolName,Kind,MediaType,Erasure,Degraded,Usage,GroupId,Used,Limit,Read,Write)", + .Type = "string" + }); + yaml.AddParameter({ + .Name = "offset", + .Description = "skip N nodes", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "limit", + .Description = "limit to N nodes", + .Type = "integer" + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer" + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Storage information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about storage"; - } -}; - -} } diff --git a/ydb/core/viewer/json_storage_usage.h b/ydb/core/viewer/viewer_storage_usage.h similarity index 59% rename from ydb/core/viewer/json_storage_usage.h rename to ydb/core/viewer/viewer_storage_usage.h index e759a72c3ecf..e1bad4fb2e1c 100644 --- a/ydb/core/viewer/json_storage_usage.h +++ b/ydb/core/viewer/viewer_storage_usage.h @@ -1,14 +1,14 @@ #pragma once +#include "json_handlers.h" #include "json_storage_base.h" +#include "viewer_pdiskinfo.h" +#include "viewer_vdiskinfo.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; -using ::google::protobuf::FieldDescriptor; - class TJsonStorageUsage : public TJsonStorageBase { using TBase = TJsonStorageBase; using TThis = TJsonStorageUsage; @@ -75,72 +75,53 @@ class TJsonStorageUsage : public TJsonStorageBase { Send(Initiator, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), std::move(json.Str())), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - type: boolean - required: false - - name: ui64 - in: query - description: return ui64 as number - type: boolean - required: false - - name: tenant - in: query - description: tenant name - type: string - required: false - - name: pool - in: query - description: storage pool name - type: string - required: false - - name: node_id - in: query - description: node id - type: integer - required: false - - name: pace - in: query - description: bucket size as a percentage - type: integer - required: false - default: 5 - - name: timeout - in: query - description: timeout in ms - type: integer - required: false - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Storage groups statistics"; - } -}; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns the distribution of groups by usage"; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Storage groups statistics", + .Description = "Returns the distribution of groups by usage", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "tenant", + .Description = "tenant name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "pool", + .Description = "storage pool name", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "node_id", + .Description = "node id", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "pace", + .Description = "bucket size as a percentage", + .Type = "integer", + .Default = "5", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; } -} diff --git a/ydb/core/viewer/json_sysinfo.h b/ydb/core/viewer/viewer_sysinfo.h similarity index 78% rename from ydb/core/viewer/json_sysinfo.h rename to ydb/core/viewer/viewer_sysinfo.h index 922f9566b917..f35c3a11fbc2 100644 --- a/ydb/core/viewer/json_sysinfo.h +++ b/ydb/core/viewer/viewer_sysinfo.h @@ -1,13 +1,7 @@ #pragma once -#include -#include -#include -#include -#include #include "json_wb_req.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { template <> class TWhiteboardMerger { @@ -55,19 +49,4 @@ struct TWhiteboardInfo { using TJsonSysInfo = TJsonWhiteboardRequest; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "System information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns system information"; - } -}; - -} } diff --git a/ydb/core/viewer/json_tabletcounters.h b/ydb/core/viewer/viewer_tabletcounters.h similarity index 78% rename from ydb/core/viewer/json_tabletcounters.h rename to ydb/core/viewer/viewer_tabletcounters.h index 023552fdea3f..60b99eb49ee7 100644 --- a/ydb/core/viewer/json_tabletcounters.h +++ b/ydb/core/viewer/viewer_tabletcounters.h @@ -1,17 +1,13 @@ #pragma once -#include -#include +#include "json_handlers.h" +#include "viewer.h" +#include "wb_aggregate.h" #include #include -#include #include #include -#include -#include "viewer.h" -#include "wb_aggregate.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -165,66 +161,47 @@ class TJsonTabletCounters : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: false - type: string - - name: tablet_id - in: query - description: tablet identifier - required: false - type: integer - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: aggregate - in: query - description: aggregate tablet counters - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Tablet counters info", + .Description = "Returns information about tablet counters", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "tablet_id", + .Description = "tablet identifier", + .Type = "integer", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "aggregate", + .Description = "aggregate tablet counters", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Tablet counters information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about tablet counters"; - } -}; - -} } diff --git a/ydb/core/viewer/json_tabletinfo.h b/ydb/core/viewer/viewer_tabletinfo.h similarity index 80% rename from ydb/core/viewer/json_tabletinfo.h rename to ydb/core/viewer/viewer_tabletinfo.h index aac8fabe01a5..57f5b71e4660 100644 --- a/ydb/core/viewer/json_tabletinfo.h +++ b/ydb/core/viewer/viewer_tabletinfo.h @@ -1,21 +1,8 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "json_pipe_req.h" #include "json_wb_req.h" -#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { template<> struct TWhiteboardInfo { @@ -95,7 +82,21 @@ class TJsonTabletInfo : public TJsonWhiteboardRequestGet()->Request.GetParams()); + TBase::RequestSettings.Timeout = FromStringWithDefault(params.Get("timeout"), 10000); + if (Database) { + RegisterWithSameMailbox(CreateBoardLookupActor(MakeEndpointsBoardPath(Database), TBase::SelfId(), EBoardLookupMode::Second)); + Become(&TThis::StateRequestedLookup, TDuration::MilliSeconds(TBase::RequestSettings.Timeout), new TEvents::TEvWakeup()); + return; + } + CheckPath(); + } + + void CheckPath() { + BLOG_TRACE("CheckPath()"); const auto& params(Event->Get()->Request.GetParams()); ReplyWithDeadTabletsInfo = params.Has("path"); if (params.Has("path")) { @@ -107,23 +108,31 @@ class TJsonTabletInfo : public TJsonWhiteboardRequestRecord.MutableDescribePath(); record->SetPath(params.Get("path")); - - TActorId txproxy = MakeTxProxyID(); - TBase::Send(txproxy, request.Release()); - UnsafeBecome(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(TBase::RequestSettings.Timeout), new TEvents::TEvWakeup()); + TBase::Send(MakeTxProxyID(), request.Release()); + Become(&TThis::StateRequestedDescribe, TDuration::MilliSeconds(TBase::RequestSettings.Timeout), new TEvents::TEvWakeup()); } else { TBase::Bootstrap(); - if (!TBase::RequestSettings.FilterFields.empty()) { - if (IsMatchesWildcard(TBase::RequestSettings.FilterFields, "(TabletId=*)")) { - TString strTabletId(TBase::RequestSettings.FilterFields.substr(10, TBase::RequestSettings.FilterFields.size() - 11)); - TTabletId uiTabletId(FromStringWithDefault(strTabletId, {})); - if (uiTabletId) { - Tablets[uiTabletId] = NKikimrTabletBase::TTabletTypes::Unknown; - Request->Record.AddFilterTabletId(uiTabletId); - } + } + } + + THolder BuildRequest() override { + THolder request = TBase::BuildRequest(); + if (!TBase::RequestSettings.FilterFields.empty()) { + if (IsMatchesWildcard(TBase::RequestSettings.FilterFields, "(TabletId=*)")) { + TString strTabletId(TBase::RequestSettings.FilterFields.substr(10, TBase::RequestSettings.FilterFields.size() - 11)); + TTabletId uiTabletId(FromStringWithDefault(strTabletId, {})); + if (uiTabletId) { + Tablets[uiTabletId] = NKikimrTabletBase::TTabletTypes::Unknown; + request->Record.AddFilterTabletId(uiTabletId); } } } + return request; + } + + void Handle(TEvStateStorage::TEvBoardInfo::TPtr& ev) { + TBase::RequestSettings.FilterNodeIds = TBase::GetNodesFromBoardReply(ev); + CheckPath(); } TString GetColumnValue(const TCell& cell, const NKikimrSchemeOp::TColumnDescription& type) { @@ -356,117 +365,19 @@ class TJsonTabletInfo : public TJsonWhiteboardRequestGetTypeRewrite()) { - hFunc(NSchemeShard::TEvSchemeShard::TEvDescribeSchemeResult, Handle); + hFunc(TEvStateStorage::TEvBoardInfo, Handle); cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } - void PassAway() override { - TBase::PassAway(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: node_id - in: query - description: node identifier - required: false - type: integer - - name: path - in: query - description: schema path - required: false - type: string - - name: merge - in: query - description: merge information from nodes - required: false - type: boolean - - name: group - in: query - description: group information by field - required: false - type: string - - name: all - in: query - description: return all possible key combinations (for enums only) - required: false - type: boolean - - name: filter - in: query - description: filter information by field - required: false - type: string - - name: alive - in: query - description: request from alive (connected) nodes only - required: false - type: boolean - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - - name: retries - in: query - description: number of retries - required: false - type: integer - - name: retry_period - in: query - description: retry period in ms - required: false - type: integer - default: 500 - - name: static - in: query - description: request from static nodes only - required: false - type: boolean - - name: since - in: query - description: filter by update time - required: false - type: string - )___"); - } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Tablet information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about tablets"; + STATEFN(StateRequestedDescribe) { + switch (ev->GetTypeRewrite()) { + hFunc(NSchemeShard::TEvSchemeShard::TEvDescribeSchemeResult, Handle); + cFunc(TEvents::TSystem::Wakeup, HandleTimeout); + } } }; } -} diff --git a/ydb/core/viewer/json_tenantinfo.h b/ydb/core/viewer/viewer_tenantinfo.h similarity index 54% rename from ydb/core/viewer/json_tenantinfo.h rename to ydb/core/viewer/viewer_tenantinfo.h index 11d785086384..f0c9301fc676 100644 --- a/ydb/core/viewer/json_tenantinfo.h +++ b/ydb/core/viewer/viewer_tenantinfo.h @@ -1,50 +1,45 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" -#include "wb_aggregate.h" -#include "wb_merge.h" #include "log.h" +#include "viewer.h" #include "viewer_request.h" +#include "viewer_tabletinfo.h" +#include "wb_aggregate.h" +#include "wb_merge.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonTenantInfo : public TViewerPipeClient { - using TBase = TViewerPipeClient; - IViewer* Viewer; +NKikimrViewer::EFlag GetViewerFlag(Ydb::Monitoring::StatusFlag::Status flag); + +class TJsonTenantInfo : public TViewerPipeClient { + using TThis = TJsonTenantInfo; + using TBase = TViewerPipeClient; + using TBase::ReplyAndPassAway; + std::optional> ListTenantsResponse; + std::unordered_map> TenantStatusResponses; + std::unordered_map> NavigateKeySetResult; + std::unordered_map> HiveDomainStats; + std::unordered_map> HiveStorageStats; + std::unordered_map> SystemStateResponse; + std::unordered_map> TabletStateResponse; + std::unordered_map> OffloadedSystemStateResponse; + std::unordered_map> OffloadedTabletStateResponse; + std::unordered_map> SelfCheckResults; + THashMap TenantByPath; THashMap TenantBySubDomainKey; THashMap HcOverallByTenantPath; - THashMap> NavigateResult; - THashMap> HiveDomainStats; - THashMap> HiveStorageStats; - NMon::TEvHttpInfo::TPtr Event; THashSet Subscribers; THashSet WhiteboardNodesRequested; THashSet OffloadTenantsRequested; THashSet MetadataCacheRequested; THashMap NodeIdsToTenant; // for tablet info - TMap WhiteboardSystemStateResponse; - THashMap> WhiteboardTabletStateResponse; TJsonSettings JsonSettings; ui32 Timeout = 0; TString User; - TString Path; TString DomainPath; bool Tablets = false; bool SystemTablets = false; @@ -52,9 +47,8 @@ class TJsonTenantInfo : public TViewerPipeClient { bool Nodes = false; bool Users = false; bool OffloadMerge = false; + bool MetadataCache = true; THashMap> TenantNodes; - THashMap OffloadMergedTabletStateResponse; - THashMap OffloadMergedSystemStateResponse; TTabletId RootHiveId = 0; TString RootId; // id of root domain (tenant) NKikimrViewer::TTenantInfo Result; @@ -65,14 +59,14 @@ class TJsonTenantInfo : public TViewerPipeClient { }; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonTenantInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) - : Viewer(viewer) - , Event(ev) - {} + : TBase(viewer, ev) + { + const auto& params(Event->Get()->Request.GetParams()); + if (Database.empty()) { + Database = params.Get("path"); + } + } TString GetLogPrefix() { static TString prefix = "json/tenantinfo "; @@ -83,12 +77,8 @@ class TJsonTenantInfo : public TViewerPipeClient { return TStringBuilder() << pathId.OwnerId << '-' << pathId.LocalPathId; } - bool IsFilterByPath() { - return !Path.empty() && DomainPath != Path; - } - bool IsValidTenant(const TString& path) { - return !IsFilterByPath() || Path == path; + return Database.empty() || Database == path; } bool IsFilterByOwner() { @@ -99,14 +89,21 @@ class TJsonTenantInfo : public TViewerPipeClient { return !IsFilterByOwner() || users.count(User) != 0; } - void Bootstrap() { - BLOG_TRACE("Bootstrap()"); + void RequestMetadataCacheHealthCheck(const TString& path) { + if (AppData()->FeatureFlags.GetEnableDbMetadataCache() && MetadataCache) { + RequestStateStorageMetadataCacheEndpointsLookup(path); + } + } + + void Bootstrap() override { + if (NeedToRedirect()) { + return; + } const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); Followers = false; Metrics = true; - InitConfig(params); Timeout = FromStringWithDefault(params.Get("timeout"), 10000); Tablets = FromStringWithDefault(params.Get("tablets"), Tablets); SystemTablets = FromStringWithDefault(params.Get("system_tablets"), Tablets); // Tablets here is by design @@ -114,35 +111,39 @@ class TJsonTenantInfo : public TViewerPipeClient { Nodes = FromStringWithDefault(params.Get("nodes"), Nodes); Users = FromStringWithDefault(params.Get("users"), Users); User = params.Get("user"); - Path = params.Get("path"); OffloadMerge = FromStringWithDefault(params.Get("offload_merge"), OffloadMerge); + MetadataCache = FromStringWithDefault(params.Get("metadata_cache"), MetadataCache); TIntrusivePtr domains = AppData()->DomainsInfo; - auto *domain = domains->GetDomain(); + auto* domain = domains->GetDomain(); + DomainPath = "/" + domain->Name; + TPathId rootPathId(domain->SchemeRoot, 1); + RootId = GetDomainId(rootPathId); + RootHiveId = domains->GetHive(); - RequestConsoleListTenants(); + if (Database.empty()) { + ListTenantsResponse = MakeRequestConsoleListTenants(); + } else { + TenantStatusResponses[Database] = MakeRequestConsoleGetTenantStatus(Database); + NavigateKeySetResult[Database] = MakeRequestSchemeCacheNavigate(Database); + } - DomainPath = "/" + domain->Name; - if (!IsFilterByPath()) { - TPathId subDomainKey(domain->SchemeRoot, 1); - NKikimrViewer::TTenant& tenant = TenantBySubDomainKey[subDomainKey]; - tenant.SetId(GetDomainId(subDomainKey)); + if (Database.empty() || Database == DomainPath) { + NKikimrViewer::TTenant& tenant = TenantBySubDomainKey[rootPathId]; + tenant.SetId(RootId); tenant.SetState(Ydb::Cms::GetDatabaseStatusResult::RUNNING); tenant.SetType(NKikimrViewer::Domain); - RequestSchemeCacheNavigate(DomainPath); - } - RootId = GetDomainId({domain->SchemeRoot, 1}); - RootHiveId = domains->GetHive(); - RequestHiveDomainStats(RootHiveId); - if (Storage) { - RequestHiveStorageStats(RootHiveId); + tenant.SetName(DomainPath); + NavigateKeySetResult[DomainPath] = MakeRequestSchemeCacheNavigate(DomainPath); + RequestMetadataCacheHealthCheck(DomainPath); } - if (Requests == 0) { - ReplyAndPassAway(); + HiveDomainStats[RootHiveId] = MakeRequestHiveDomainStats(RootHiveId); + if (Storage) { + HiveStorageStats[RootHiveId] = MakeRequestHiveStorageStats(RootHiveId); } - Become(&TThis::StateRequested, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); + Become(&TThis::StateCollectingInfo, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup()); } void PassAway() override { @@ -152,10 +153,9 @@ class TJsonTenantInfo : public TViewerPipeClient { } } TBase::PassAway(); - BLOG_TRACE("PassAway()"); } - STATEFN(StateRequested) { + STATEFN(StateCollectingInfo) { switch (ev->GetTypeRewrite()) { hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle); hFunc(NConsole::TEvConsole::TEvGetTenantStatusResponse, Handle); @@ -167,42 +167,65 @@ class TJsonTenantInfo : public TViewerPipeClient { hFunc(TEvViewer::TEvViewerResponse, Handle); hFunc(TEvents::TEvUndelivered, Undelivered); hFunc(TEvInterconnect::TEvNodeDisconnected, Disconnected); - hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle); + hFunc(TEvTabletPipe::TEvClientConnected, Handle); hFunc(TEvStateStorage::TEvBoardInfo, Handle); hFunc(NHealthCheck::TEvSelfCheckResultProto, Handle); cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } + void Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev) { + if (ev->Get()->Status != NKikimrProto::OK) { + TString error = TStringBuilder() << "Failed to establish pipe: " << NKikimrProto::EReplyStatus_Name(ev->Get()->Status); + if (ev->Get()->TabletId == GetConsoleId()) { + if (ListTenantsResponse) { + ListTenantsResponse->Error(error); + } + for (auto& [path, response] : TenantStatusResponses) { + response.Error(error); + } + } + { + auto it = HiveDomainStats.find(ev->Get()->TabletId); + if (it != HiveDomainStats.end()) { + it->second.Error(error); + } + } + { + auto it = HiveStorageStats.find(ev->Get()->TabletId); + if (it != HiveStorageStats.end()) { + it->second.Error(error); + } + } + } + TBase::Handle(ev); // all RequestDone() are handled by base handler + } + void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) { - BLOG_TRACE("Received ListTenantsResponse"); + ListTenantsResponse->Set(std::move(ev)); Ydb::Cms::ListDatabasesResult listTenantsResult; - ev->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); + ListTenantsResponse->Get()->Record.GetResponse().operation().result().UnpackTo(&listTenantsResult); for (const TString& path : listTenantsResult.paths()) { - if (!IsValidTenant(path)) { - continue; - } - RequestConsoleGetTenantStatus(path); - RequestSchemeCacheNavigate(path); - - if (AppData()->FeatureFlags.GetEnableDbMetadataCache()) { - RequestStateStorageMetadataCacheEndpointsLookup(path); - } + TenantStatusResponses[path] = MakeRequestConsoleGetTenantStatus(path); + NavigateKeySetResult[path] = MakeRequestSchemeCacheNavigate(path); + RequestMetadataCacheHealthCheck(path); } RequestDone(); } void Handle(NConsole::TEvConsole::TEvGetTenantStatusResponse::TPtr& ev) { - BLOG_TRACE("Received GetTenantStatusResponse"); Ydb::Cms::GetDatabaseStatusResult getTenantStatusResult; ev->Get()->Record.GetResponse().operation().result().UnpackTo(&getTenantStatusResult); TString path = getTenantStatusResult.path(); + TenantStatusResponses[path].Set(std::move(ev)); NKikimrViewer::TTenant& tenant = TenantByPath[path]; tenant.SetName(path); tenant.SetState(getTenantStatusResult.state()); if (getTenantStatusResult.has_required_shared_resources()) { tenant.SetType(NKikimrViewer::Shared); - RequestSchemeCacheNavigate(path); + if (NavigateKeySetResult.count(path) == 0) { + NavigateKeySetResult[path] = MakeRequestSchemeCacheNavigate(path); + } } for (const Ydb::Cms::StorageUnits& unit : getTenantStatusResult.allocated_resources().storage_units()) { NKikimrViewer::TTenantResource& resource = *tenant.MutableResources()->AddAllocated(); @@ -239,18 +262,25 @@ class TJsonTenantInfo : public TViewerPipeClient { void SendWhiteboardSystemStateRequest(const TNodeId nodeId) { Subscribers.insert(nodeId); TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); - THolder request = MakeHolder(); - BLOG_TRACE("Tenant " << NodeIdsToTenant[nodeId] << " send to " << nodeId << " TEvSystemStateRequest: " << request->Record.ShortDebugString()); - SendRequest(whiteboardServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); + if (SystemStateResponse.count(nodeId) == 0) { + SystemStateResponse.emplace(nodeId, MakeRequest(whiteboardServiceId, + new TEvWhiteboard::TEvSystemStateRequest(), + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, + nodeId)); + } } void SendWhiteboardTabletStateRequest(const TNodeId nodeId) { Subscribers.insert(nodeId); TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); - THolder request = MakeHolder(); - request->Record.SetFormat("packed5"); - BLOG_TRACE("Tenant " << NodeIdsToTenant[nodeId] << " send to " << nodeId << " TEvTabletStateRequest: " << request->Record.ShortDebugString()); - SendRequest(whiteboardServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); + if (TabletStateResponse.count(nodeId) == 0) { + auto request = std::make_unique(); + request->Record.SetFormat("packed5"); + TabletStateResponse.emplace(nodeId, MakeRequest(whiteboardServiceId, + request.release(), + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, + nodeId)); + } } void SendWhiteboardRequests(const TNodeId nodeId) { @@ -269,19 +299,14 @@ class TJsonTenantInfo : public TViewerPipeClient { auto itPos = std::next(nodesIds.begin(), hash % nodesIds.size()); std::nth_element(nodesIds.begin(), itPos, nodesIds.end()); TNodeId nodeId = *itPos; - Subscribers.insert(nodeId); - TActorId viewerServiceId = MakeViewerID(nodeId); - THolder sysRequest = MakeHolder(); sysRequest->Record.MutableSystemRequest(); sysRequest->Record.SetTimeout(Timeout / 3); for (auto nodeId : nodesIds) { sysRequest->Record.MutableLocation()->AddNodeId(nodeId); } - BLOG_TRACE("Tenant " << tenantId << " send to " << nodeId << " TEvViewerRequest: " << sysRequest->Record.ShortDebugString()); - ViewerWhiteboardCookie cookie (NKikimrViewer::TEvViewerRequest::kSystemRequest, nodeId); - SendRequest(viewerServiceId, sysRequest.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, cookie.ToUi64()); + OffloadedSystemStateResponse[nodeId] = MakeRequestViewer(nodeId, sysRequest.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession); if (Tablets) { THolder tblRequest = MakeHolder(); @@ -290,15 +315,15 @@ class TJsonTenantInfo : public TViewerPipeClient { for (auto nodeId : nodesIds) { tblRequest->Record.MutableLocation()->AddNodeId(nodeId); } - BLOG_TRACE("Tenant " << tenantId << " send to " << nodeId << " TEvViewerRequest: " << tblRequest->Record.ShortDebugString()); - ViewerWhiteboardCookie cookie(NKikimrViewer::TEvViewerRequest::kTabletRequest, nodeId); - SendRequest(viewerServiceId, tblRequest.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, cookie.ToUi64()); + OffloadedTabletStateResponse[nodeId] = MakeRequestViewer(nodeId, tblRequest.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession); } } } void Handle(TEvHive::TEvResponseHiveDomainStats::TPtr& ev) { - for (const NKikimrHive::THiveDomainStats& hiveStat : ev->Get()->Record.GetDomainStats()) { + auto& response = HiveDomainStats[ev->Cookie]; + response.Set(std::move(ev)); + for (const NKikimrHive::THiveDomainStats& hiveStat : response.Get()->Record.GetDomainStats()) { TPathId subDomainKey({hiveStat.GetShardId(), hiveStat.GetPathId()}); NKikimrViewer::TTenant& tenant = TenantBySubDomainKey[subDomainKey]; TString tenantId = GetDomainId({hiveStat.GetShardId(), hiveStat.GetPathId()}); @@ -318,7 +343,6 @@ class TJsonTenantInfo : public TViewerPipeClient { } } - BLOG_TRACE("Received HiveDomainStats for " << tenant.GetId() << " from " << ev->Cookie); std::vector nodesIds; nodesIds.reserve(hiveStat.NodeIdsSize()); for (auto nodeId : hiveStat.GetNodeIds()) { @@ -327,32 +351,40 @@ class TJsonTenantInfo : public TViewerPipeClient { } TenantNodes[tenantId] = nodesIds; - if (OffloadMerge) { - SendOffloadRequests(tenantId); - } else { - for (TNodeId nodeId : hiveStat.GetNodeIds()) { - SendWhiteboardRequests(nodeId); + if (Database.empty() || Database == tenant.GetName()) { + if (OffloadMerge) { + SendOffloadRequests(tenantId); + } else { + for (TNodeId nodeId : hiveStat.GetNodeIds()) { + SendWhiteboardRequests(nodeId); + } } } } - HiveDomainStats[ev->Cookie] = std::move(ev->Release()); + RequestDone(); } void Handle(TEvHive::TEvResponseHiveStorageStats::TPtr& ev) { - BLOG_TRACE("Received HiveStorageStats from " << ev->Cookie); - HiveStorageStats[ev->Cookie] = std::move(ev->Release()); + HiveStorageStats[ev->Cookie].Set(std::move(ev)); RequestDone(); } void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { - if (ev->Get()->Request->ResultSet.size() == 1 && ev->Get()->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { - auto domainInfo = ev->Get()->Request->ResultSet.begin()->DomainInfo; + TString path = GetPath(ev); + auto& result(NavigateKeySetResult[path]); + result.Set(std::move(ev)); + if (result.IsOk()) { + auto domainInfo = result.Get()->Request->ResultSet.begin()->DomainInfo; TTabletId hiveId = domainInfo->Params.GetHive(); if (hiveId) { - RequestHiveDomainStats(hiveId); + if (HiveDomainStats.count(hiveId) == 0) { + HiveDomainStats[hiveId] = MakeRequestHiveDomainStats(hiveId); + } if (Storage) { - RequestHiveStorageStats(hiveId); + if (HiveStorageStats.count(hiveId) == 0) { + HiveStorageStats[hiveId] = MakeRequestHiveStorageStats(hiveId); + } } } NKikimrViewer::TTenant& tenant = TenantBySubDomainKey[domainInfo->DomainKey]; @@ -366,40 +398,35 @@ class TJsonTenantInfo : public TViewerPipeClient { tenant.SetResourceId(GetDomainId(domainInfo->ResourcesDomainKey)); } TString id = GetDomainId(domainInfo->DomainKey); - TString path = CanonizePath(ev->Get()->Request->ResultSet.begin()->Path); - BLOG_TRACE("Received Navigate for " << id << " " << path); tenant.SetId(id); tenant.SetName(path); if (tenant.GetType() == NKikimrViewer::UnknownTenantType) { tenant.SetType(NKikimrViewer::Dedicated); } - NavigateResult[id] = std::move(ev->Get()->Request); } RequestDone(); } void Handle(NNodeWhiteboard::TEvWhiteboard::TEvSystemStateResponse::TPtr& ev) { ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Received TEvSystemStateResponse from " << nodeId); - WhiteboardSystemStateResponse[nodeId] = std::move(ev->Get()->Record); + SystemStateResponse[nodeId].Set(std::move(ev)); RequestDone(); } void Handle(NNodeWhiteboard::TEvWhiteboard::TEvTabletStateResponse::TPtr& ev) { ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Received TEvTabletStateResponse from " << nodeId << " with " - << TWhiteboardInfo::GetElementsCount(ev->Get()->Record) << " tablets"); - auto tenantId = NodeIdsToTenant[nodeId]; - WhiteboardTabletStateResponse[tenantId][nodeId] = std::move(ev->Get()->Record); + TabletStateResponse[nodeId].Set(std::move(ev)); RequestDone(); } void Handle(NHealthCheck::TEvSelfCheckResultProto::TPtr& ev) { - auto result = std::move(ev->Get()->Record); + TNodeId nodeId = ev->Cookie; + auto& selfCheckResult(SelfCheckResults[nodeId]); + selfCheckResult.Set(std::move(ev)); + auto& result(selfCheckResult.Get()->Record); if (result.database_status_size() == 1) { HcOverallByTenantPath.emplace(result.database_status(0).name(), GetViewerFlag(result.database_status(0).overall())); } - RequestDone(); } @@ -408,9 +435,9 @@ class TJsonTenantInfo : public TViewerPipeClient { if (activeNode != 0) { Subscribers.insert(activeNode); std::optional cache = MakeDatabaseMetadataCacheId(activeNode); - auto request = MakeHolder(); if (MetadataCacheRequested.insert(ev->Get()->Path).second) { - SendRequest(*cache, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, activeNode); + SelfCheckResults[activeNode] = MakeRequest(*cache, new NHealthCheck::TEvSelfCheckRequestProto, + IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, activeNode); } } RequestDone(); @@ -421,15 +448,11 @@ class TJsonTenantInfo : public TViewerPipeClient { auto tenantId = NodeIdsToTenant[nodeId]; switch (ev->Get()->Record.GetResponseCase()) { case NKikimrViewer::TEvViewerResponse::kTabletResponse: - BLOG_TRACE("Received TEvViewerResponse from " << nodeId << " with " - << TWhiteboardInfo::GetElementsCount(ev->Get()->Record.GetTabletResponse()) - << " tablets"); - OffloadMergedTabletStateResponse[tenantId] = std::move(ev->Get()->Record); + OffloadedTabletStateResponse[nodeId].Set(std::move(ev)); RequestDone(); break; case NKikimrViewer::TEvViewerResponse::kSystemResponse: - BLOG_TRACE("Received TEvViewerResponse from " << nodeId); - OffloadMergedSystemStateResponse[tenantId] = std::move(ev->Get()->Record); + OffloadedSystemStateResponse[nodeId].Set(std::move(ev)); RequestDone(); break; default: @@ -437,94 +460,53 @@ class TJsonTenantInfo : public TViewerPipeClient { } } - void Undelivered(TEvents::TEvUndelivered::TPtr &ev) { - if (ev->Get()->SourceType == NHealthCheck::EvSelfCheckRequestProto) { - ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Undelivered for node " << nodeId << " event " << ev->Get()->SourceType); - auto tenantId = NodeIdsToTenant[nodeId]; - if (HcOverallByTenantPath.emplace(tenantId, NKikimrViewer::EFlag::Grey).second) { - RequestDone(); - } + template + bool HasUnfinishedRequest(std::unordered_map& responses, TNodeId nodeId) { + auto itRequest = responses.find(nodeId); + if (itRequest != responses.end()) { + return !itRequest->second.IsDone(); } - if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvSystemStateRequest) { - ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Undelivered for node " << nodeId << " event " << ev->Get()->SourceType); - if (WhiteboardSystemStateResponse.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - } - if (ev->Get()->SourceType == NNodeWhiteboard::TEvWhiteboard::EvTabletStateRequest) { - ui32 nodeId = ev.Get()->Cookie; - BLOG_TRACE("Undelivered for node " << nodeId << " event " << ev->Get()->SourceType); - auto tenantId = NodeIdsToTenant[nodeId]; - if (WhiteboardTabletStateResponse[tenantId].emplace(nodeId, NKikimrWhiteboard::TEvTabletStateResponse{}).second) { + return false; + } + + template + void ErrorRequest(std::unordered_map& responses, TNodeId nodeId, const TString& error) { + auto itRequest = responses.find(nodeId); + if (itRequest != responses.end()) { + if (itRequest->second.Error(error)) { RequestDone(); } } - if (ev->Get()->SourceType == NViewer::TEvViewer::EvViewerRequest) { - ViewerWhiteboardCookie cookie(ev.Get()->Cookie); - auto nodeId = cookie.GetNodeId(); - auto tenantId = NodeIdsToTenant[nodeId]; - BLOG_TRACE("Undelivered for node " << cookie.GetNodeId() << " event " << ev->Get()->SourceType); - switch (cookie.GetRequestCase()) { - case NKikimrViewer::TEvViewerRequest::kTabletRequest: - if (OffloadMergedTabletStateResponse.emplace(tenantId, NKikimrViewer::TEvViewerResponse{}).second) { - // fallback - for (TNodeId nodeId : TenantNodes[tenantId]) { - SendWhiteboardTabletStateRequest(nodeId); - } - RequestDone(); - }; - - break; - case NKikimrViewer::TEvViewerRequest::kSystemRequest: - if (OffloadMergedSystemStateResponse.emplace(tenantId, NKikimrViewer::TEvViewerResponse{}).second) { - // fallback - for (TNodeId nodeId : TenantNodes[tenantId]) { - SendWhiteboardSystemStateRequest(nodeId); - } - RequestDone(); - } - break; - default: - break; - } - } } - void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr &ev) { - TNodeId nodeId = ev->Get()->NodeId; - auto tenantId = NodeIdsToTenant[nodeId]; - BLOG_TRACE("NodeDisconnected for nodeId " << nodeId); - - if (OffloadTenantsRequested.count(tenantId) > 0) { + void ErrorRequests(TNodeId nodeId, const TString& error) { + if (HasUnfinishedRequest(OffloadedSystemStateResponse, nodeId)) { // fallback - if (OffloadMergedSystemStateResponse.emplace(tenantId, NKikimrViewer::TEvViewerResponse{}).second) { - for (TNodeId nodeId : TenantNodes[tenantId]) { - SendWhiteboardSystemStateRequest(nodeId); - } - RequestDone(); - } - if (Tablets && OffloadMergedSystemStateResponse.emplace(tenantId, NKikimrViewer::TEvViewerResponse{}).second) { - for (TNodeId nodeId : TenantNodes[tenantId]) { - SendWhiteboardSystemStateRequest(nodeId); - } - RequestDone(); + for (TNodeId nodeId : TenantNodes[NodeIdsToTenant[nodeId]]) { + SendWhiteboardSystemStateRequest(nodeId); } } - if (WhiteboardNodesRequested.count(nodeId) > 0) { - if (WhiteboardSystemStateResponse.emplace(nodeId, NKikimrWhiteboard::TEvSystemStateResponse{}).second) { - RequestDone(); - } - if (Tablets && WhiteboardTabletStateResponse[tenantId].emplace(nodeId, NKikimrWhiteboard::TEvTabletStateResponse{}).second) { - RequestDone(); - } - } - if (MetadataCacheRequested.count(tenantId) > 0) { - if (HcOverallByTenantPath.emplace(tenantId, NKikimrViewer::EFlag::Grey).second) { - RequestDone(); + if (HasUnfinishedRequest(OffloadedTabletStateResponse, nodeId)) { + // fallback + for (TNodeId nodeId : TenantNodes[NodeIdsToTenant[nodeId]]) { + SendWhiteboardTabletStateRequest(nodeId); } } + ErrorRequest(SelfCheckResults, nodeId, error); + ErrorRequest(SystemStateResponse, nodeId, error); + ErrorRequest(TabletStateResponse, nodeId, error); + ErrorRequest(OffloadedSystemStateResponse, nodeId, error); + ErrorRequest(OffloadedTabletStateResponse, nodeId, error); + } + + void Undelivered(TEvents::TEvUndelivered::TPtr &ev) { + ui32 nodeId = ev.Get()->Cookie; + ErrorRequests(nodeId, "undelivered"); + } + + void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { + TNodeId nodeId = ev->Get()->NodeId; + ErrorRequests(nodeId, "disconnected"); } NKikimrViewer::TStorageUsage::EType GetStorageType(const TString& poolKind) { @@ -538,35 +520,68 @@ class TJsonTenantInfo : public TViewerPipeClient { return NKikimrViewer::TStorageUsage::None; } - void ReplyAndPassAway() { - BLOG_TRACE("ReplyAndPassAway() started"); - TIntrusivePtr domains = AppData()->DomainsInfo; - auto *domain = domains->GetDomain(); + NKikimrViewer::TStorageUsage::EType GuessStorageType(const NKikimrSubDomains::TDomainDescription& domainDescription) { + NKikimrViewer::TStorageUsage::EType type = NKikimrViewer::TStorageUsage::SSD; + for (const auto& pool : domainDescription.GetStoragePools()) { + auto poolType = GetStorageType(pool.GetKind()); + if (poolType != NKikimrViewer::TStorageUsage::None) { + type = poolType; + break; + } + } + return type; + } + + void ReplyAndPassAway() override { + Result.SetVersion(2); THashMap OverallByDomainId; - TMap NodeSystemStateInfo; + std::unordered_map nodeSystemStateInfo; - for (auto& [tenantId, record] : OffloadMergedSystemStateResponse) { - for (auto& systemState : *(record.MutableSystemResponse()->MutableSystemStateInfo())) { - auto ni = systemState.GetNodeId(); - NodeSystemStateInfo[ni] = std::move(systemState); + for (const auto& [nodeId, request] : OffloadedSystemStateResponse) { + if (request.IsOk()) { + for (const auto& systemState : request.Get()->Record.GetSystemResponse().GetSystemStateInfo()) { + nodeSystemStateInfo[systemState.GetNodeId()] = &systemState; + } } } - for (auto& [nodeId, record] : WhiteboardSystemStateResponse) { - if (record.SystemStateInfoSize() == 1) { - NodeSystemStateInfo[nodeId] = std::move(record.GetSystemStateInfo(0)); + + for (const auto& [nodeId, request] : SystemStateResponse) { + if (request.IsOk()) { + nodeSystemStateInfo[nodeId] = &(request.Get()->Record.GetSystemStateInfo(0)); } } - for (const auto& [subDomainKey, tenantBySubDomainKey] : TenantBySubDomainKey) { + TString name(tenantBySubDomainKey.GetName()); + if (!IsValidTenant(name)) { + continue; + } TString id(GetDomainId(subDomainKey)); NKikimrWhiteboard::TEvTabletStateResponse tabletInfo; THashMap tabletInfoIndex; + if (Tablets) { - if (WhiteboardTabletStateResponse[id].size() > 0) { - TWhiteboardInfo::MergeResponses(tabletInfo, WhiteboardTabletStateResponse[id]); - } else if (OffloadMerge) { - tabletInfo = std::move(*(OffloadMergedTabletStateResponse[id].MutableTabletResponse())); + const auto& tenantNodes(TenantNodes[name]); + bool hasTabletInfo = false; + for (TNodeId nodeId : tenantNodes) { + auto it = OffloadedTabletStateResponse.find(nodeId); + if (it != OffloadedTabletStateResponse.end() && it->second.IsOk()) { + tabletInfo = std::move(*(it->second.Get()->Record.MutableTabletResponse())); + hasTabletInfo = true; + break; + } } + + if (!hasTabletInfo) { + TMap tabletInfoForMerge; + for (TNodeId nodeId : tenantNodes) { + auto it = TabletStateResponse.find(nodeId); + if (it != TabletStateResponse.end() && it->second.IsOk()) { + tabletInfoForMerge.emplace(nodeId, std::move(it->second.Get()->Record)); + } + } + TWhiteboardInfo::MergeResponses(tabletInfo, tabletInfoForMerge); + } + if (SystemTablets) { for (const auto& info : TWhiteboardInfo::GetElementsField(tabletInfo)) { tabletInfoIndex[info.GetTabletId()] = &info; @@ -575,13 +590,9 @@ class TJsonTenantInfo : public TViewerPipeClient { } NKikimrViewer::EFlag overall = NKikimrViewer::EFlag::Grey; - auto itNavigate = NavigateResult.find(id); - if (itNavigate != NavigateResult.end()) { - NSchemeCache::TSchemeCacheNavigate::TEntry entry = itNavigate->second->ResultSet.front(); - TString path = CanonizePath(entry.Path); - if (!IsValidTenant(path)) { - continue; - } + auto itNavigate = NavigateKeySetResult.find(name); + if (itNavigate != NavigateKeySetResult.end() && itNavigate->second.IsOk()) { + NSchemeCache::TSchemeCacheNavigate::TEntry entry = itNavigate->second.Get()->Request->ResultSet.front(); std::unordered_set users; if(!User.empty() || Users) { if (entry.SecurityObject) { @@ -597,7 +608,7 @@ class TJsonTenantInfo : public TViewerPipeClient { } } NKikimrViewer::TTenant& tenant = *Result.AddTenantInfo(); - auto itTenantByPath = TenantByPath.find(path); + auto itTenantByPath = TenantByPath.find(name); if (itTenantByPath != TenantByPath.end()) { tenant = std::move(itTenantByPath->second); TenantByPath.erase(itTenantByPath); @@ -626,35 +637,41 @@ class TJsonTenantInfo : public TViewerPipeClient { tenant.MutableUserAttributes()->insert({userAttribute.first, userAttribute.second}); } + TIntrusivePtr domains = AppData()->DomainsInfo; + auto* domain = domains->GetDomain(); TStackVec tablets; - for (TTabletId tabletId : entry.DomainInfo->Params.GetCoordinators()) { - tablets.emplace_back(tabletId); - } - for (TTabletId tabletId : entry.DomainInfo->Params.GetMediators()) { - tablets.emplace_back(tabletId); - } - if (entry.DomainInfo->Params.HasSchemeShard()) { - tablets.emplace_back(entry.DomainInfo->Params.GetSchemeShard()); - } else { - tablets.emplace_back(domain->SchemeRoot); - tablets.emplace_back(MakeBSControllerID()); - tablets.emplace_back(MakeConsoleID()); - } - TTabletId hiveId = domains->GetHive(); - if (entry.DomainInfo->Params.HasHive()) { - hiveId = entry.DomainInfo->Params.GetHive(); - } else { - if (tenant.GetType() == NKikimrViewer::Serverless) { - auto itResourceNavigate = NavigateResult.find(tenant.GetResourceId()); - if (itResourceNavigate != NavigateResult.end()) { - NSchemeCache::TSchemeCacheNavigate::TEntry entry = itResourceNavigate->second->ResultSet.front(); - if (entry.DomainInfo->Params.HasHive()) { - hiveId = entry.DomainInfo->Params.GetHive(); + TTabletId hiveId = {}; + if (entry.DomainInfo) { + for (TTabletId tabletId : entry.DomainInfo->Params.GetCoordinators()) { + tablets.emplace_back(tabletId); + } + for (TTabletId tabletId : entry.DomainInfo->Params.GetMediators()) { + tablets.emplace_back(tabletId); + } + if (entry.DomainInfo->Params.HasSchemeShard()) { + tablets.emplace_back(entry.DomainInfo->Params.GetSchemeShard()); + } else { + tablets.emplace_back(domain->SchemeRoot); + } + if (entry.DomainInfo->Params.HasHive()) { + tablets.emplace_back(hiveId = entry.DomainInfo->Params.GetHive()); + } else { + if (tenant.GetType() == NKikimrViewer::Serverless) { + auto itResourceNavigate = NavigateKeySetResult.find(tenant.GetResourceId()); + if (itResourceNavigate != NavigateKeySetResult.end() && itResourceNavigate->second.IsOk()) { + NSchemeCache::TSchemeCacheNavigate::TEntry entry = itResourceNavigate->second.Get()->Request->ResultSet.front(); + if (entry.DomainInfo->Params.HasHive()) { + tablets.emplace_back(hiveId = entry.DomainInfo->Params.GetHive()); + } } + } else { + tablets.emplace_back(hiveId = domains->GetHive()); } } + } else { + tablets.emplace_back(domain->SchemeRoot); + tablets.emplace_back(hiveId = domains->GetHive()); } - tablets.emplace_back(hiveId); if (SystemTablets) { for (TTabletId tabletId : tablets) { @@ -670,38 +687,42 @@ class TJsonTenantInfo : public TViewerPipeClient { } if (Storage) { + THashMap> databaseStorageByType; auto itHiveStorageStats = HiveStorageStats.find(hiveId); - if (itHiveStorageStats != HiveStorageStats.end()) { + if (itHiveStorageStats != HiveStorageStats.end() && itHiveStorageStats->second.IsOk()) { const NKikimrHive::TEvResponseHiveStorageStats& record = itHiveStorageStats->second.Get()->Record; - uint64 storageAllocatedSize = 0; - uint64 storageAvailableSize = 0; - uint64 storageMinAvailableSize = std::numeric_limits::max(); - uint64 storageGroups = 0; - for (const NKikimrHive::THiveStoragePoolStats& poolStat : record.GetPools()) { - if (poolStat.GetName().StartsWith(tenantBySubDomainKey.GetName())) { - for (const NKikimrHive::THiveStorageGroupStats& groupStat : poolStat.GetGroups()) { - storageAllocatedSize += groupStat.GetAllocatedSize(); - storageAvailableSize += groupStat.GetAvailableSize(); - storageMinAvailableSize = std::min(storageMinAvailableSize, groupStat.GetAvailableSize()); - ++storageGroups; + if (entry.DomainDescription) { + uint64 storageAllocatedSize = 0; + uint64 storageAvailableSize = 0; + uint64 storageMinAvailableSize = std::numeric_limits::max(); + uint64 storageGroups = 0; + std::unordered_map storagePoolType; + for (const auto& storagePool : entry.DomainDescription->Description.GetStoragePools()) { + storagePoolType[storagePool.GetName()] = GetStorageType(storagePool.GetKind()); + } + for (const NKikimrHive::THiveStoragePoolStats& poolStat : record.GetPools()) { + if (storagePoolType.count(poolStat.GetName())) { + NKikimrViewer::TStorageUsage::EType storageType = storagePoolType[poolStat.GetName()]; + for (const NKikimrHive::THiveStorageGroupStats& groupStat : poolStat.GetGroups()) { + storageAllocatedSize += groupStat.GetAllocatedSize(); + storageAvailableSize += groupStat.GetAvailableSize(); + databaseStorageByType[storageType].first += groupStat.GetAllocatedSize(); + databaseStorageByType[storageType].second += groupStat.GetAvailableSize(); + storageMinAvailableSize = std::min(storageMinAvailableSize, groupStat.GetAvailableSize()); + ++storageGroups; + } } } + uint64 storageAllocatedLimit = storageAllocatedSize + storageAvailableSize; + tenant.SetStorageAllocatedSize(storageAllocatedSize); + tenant.SetStorageAllocatedLimit(storageAllocatedLimit); + tenant.SetStorageMinAvailableSize(storageMinAvailableSize); + tenant.SetStorageGroups(storageGroups); } - uint64 storageAllocatedLimit = storageAllocatedSize + storageAvailableSize; - tenant.SetStorageAllocatedSize(storageAllocatedSize); - tenant.SetStorageAllocatedLimit(storageAllocatedLimit); - tenant.SetStorageMinAvailableSize(storageMinAvailableSize); - tenant.SetStorageGroups(storageGroups); } - THashMap storageUsageByType; + THashMap tablesStorageByType; THashMap storageQuotasByType; - if (entry.DomainDescription) { - for (const auto& poolUsage : entry.DomainDescription->Description.GetDiskSpaceUsage().GetStoragePoolsUsage()) { - auto type = GetStorageType(poolUsage.GetPoolKind()); - storageUsageByType[type] += poolUsage.GetTotalSize(); - } - } for (const auto& quota : tenant.GetDatabaseQuotas().storage_quotas()) { auto type = GetStorageType(quota.unit_kind()); @@ -710,28 +731,54 @@ class TJsonTenantInfo : public TViewerPipeClient { usage.HardQuota += quota.data_size_hard_quota(); } - for (const auto& [type, size] : storageUsageByType) { - auto& storageUsage = *tenant.AddStorageUsage(); - storageUsage.SetType(type); - storageUsage.SetSize(size); + if (entry.DomainDescription) { + for (const auto& poolUsage : entry.DomainDescription->Description.GetDiskSpaceUsage().GetStoragePoolsUsage()) { + auto type = GetStorageType(poolUsage.GetPoolKind()); + tablesStorageByType[type] += poolUsage.GetTotalSize(); + } + + if (tablesStorageByType.empty() && entry.DomainDescription->Description.HasDiskSpaceUsage()) { + tablesStorageByType[GuessStorageType(entry.DomainDescription->Description)] = + entry.DomainDescription->Description.GetDiskSpaceUsage().GetTables().GetTotalSize(); + } + + if (storageQuotasByType.empty()) { + auto& quotas = storageQuotasByType[GuessStorageType(entry.DomainDescription->Description)]; + quotas.HardQuota = entry.DomainDescription->Description.GetDatabaseQuotas().data_size_hard_quota(); + quotas.SoftQuota = entry.DomainDescription->Description.GetDatabaseQuotas().data_size_soft_quota(); + } + } + + for (const auto& [type, size] : tablesStorageByType) { auto it = storageQuotasByType.find(type); + auto& tablesStorage = *tenant.AddTablesStorage(); + tablesStorage.SetType(type); + tablesStorage.SetSize(size); if (it != storageQuotasByType.end()) { - storageUsage.SetLimit(it->second.HardQuota); - storageUsage.SetSoftQuota(it->second.SoftQuota); - storageUsage.SetHardQuota(it->second.HardQuota); + tablesStorage.SetLimit(it->second.SoftQuota); + tablesStorage.SetSoftQuota(it->second.SoftQuota); + tablesStorage.SetHardQuota(it->second.HardQuota); } } + + for (const auto& [type, pr] : databaseStorageByType) { + auto& databaseStorage = *tenant.AddDatabaseStorage(); + databaseStorage.SetType(type); + databaseStorage.SetSize(pr.first); + databaseStorage.SetLimit(pr.first + pr.second); + } } THashSet tenantNodes; for (TNodeId nodeId : tenant.GetNodeIds()) { - auto itNodeInfo = NodeSystemStateInfo.find(nodeId); - if (itNodeInfo != NodeSystemStateInfo.end()) { + auto itNodeInfo = nodeSystemStateInfo.find(nodeId); + if (itNodeInfo != nodeSystemStateInfo.end()) { + const auto& nodeInfo(*(itNodeInfo->second)); if (Nodes) { - tenant.AddNodes()->CopyFrom(itNodeInfo->second); + tenant.AddNodes()->CopyFrom(nodeInfo); } - for (const auto& poolStat : itNodeInfo->second.GetPoolStats()) { + for (const auto& poolStat : nodeInfo.GetPoolStats()) { TString poolName = poolStat.GetName(); NKikimrWhiteboard::TSystemStateInfo_TPoolStats* targetPoolStat = nullptr; for (NKikimrWhiteboard::TSystemStateInfo_TPoolStats& ps : *tenant.MutablePoolStats()) { @@ -754,17 +801,16 @@ class TJsonTenantInfo : public TViewerPipeClient { } tenant.SetCoresUsed(tenant.GetCoresUsed() + poolStat.GetUsage() * poolStat.GetThreads()); } - if (itNodeInfo->second.HasMemoryUsed()) { - tenant.SetMemoryUsed(tenant.GetMemoryUsed() + itNodeInfo->second.GetMemoryUsed()); + if (nodeInfo.HasMemoryUsed()) { + tenant.SetMemoryUsed(tenant.GetMemoryUsed() + nodeInfo.GetMemoryUsed()); } - if (itNodeInfo->second.HasMemoryLimit()) { - tenant.SetMemoryLimit(tenant.GetMemoryLimit() + itNodeInfo->second.GetMemoryLimit()); + if (nodeInfo.HasMemoryLimit()) { + tenant.SetMemoryLimit(tenant.GetMemoryLimit() + nodeInfo.GetMemoryLimit()); } - overall = Max(overall, GetViewerFlag(itNodeInfo->second.GetSystemState())); + overall = Max(overall, GetViewerFlag(nodeInfo.GetSystemState())); } tenantNodes.emplace(nodeId); } - if (tenant.GetType() == NKikimrViewer::Serverless) { tenant.SetStorageAllocatedSize(tenant.GetMetrics().GetStorage()); const bool noExclusiveNodes = tenantNodes.empty(); @@ -791,9 +837,10 @@ class TJsonTenantInfo : public TViewerPipeClient { tablet.SetCount(prTabletCount); } } - if (HcOverallByTenantPath.count(path) > 0 && HcOverallByTenantPath[path] != NKikimrViewer::EFlag::Grey) { - tenant.SetOverall(HcOverallByTenantPath[path]); - OverallByDomainId[tenant.GetId()] = HcOverallByTenantPath[path]; + auto itOverall = HcOverallByTenantPath.find(name); + if (itOverall != HcOverallByTenantPath.end()) { + tenant.SetOverall(itOverall->second); + OverallByDomainId[tenant.GetId()] = itOverall->second; } else { tenant.SetOverall(overall); OverallByDomainId[tenant.GetId()] = overall; @@ -833,105 +880,84 @@ class TJsonTenantInfo : public TViewerPipeClient { }); TStringStream json; TProtoToJson::ProtoToJson(json, Result, JsonSettings); - Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); - PassAway(); + ReplyAndPassAway(GetHTTPOKJSON(json.Str())); } void HandleTimeout() { - BLOG_TRACE("Timeout occurred"); - Result.AddErrors("Timeout occurred"); + Result.AddErrors("timeout"); ReplyAndPassAway(); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; - -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: false - type: string - - name: user - in: query - description: tenant owner - required: false - type: string - - name: followers - in: query - description: return followers - required: false - type: boolean - - name: metrics - in: query - description: return tablet metrics - required: false - type: boolean - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: tablets - in: query - description: return tablets - required: false - type: boolean - - name: system_tablets - in: query - description: return system tablets - required: false - type: boolean - - name: offload_merge - in: query - description: use offload merge - required: false - type: boolean - - name: storage - in: query - description: return storage info - required: false - type: boolean - - name: nodes - in: query - description: return nodes info - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "\"Tenant info (detailed)\""; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Tenant info (detailed)", + .Description = "Returns information about tenants", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "user", + .Description = "tenant owner", + .Type = "string", + }); + yaml.AddParameter({ + .Name = "followers", + .Description = "return followers", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "metrics", + .Description = "return tablet metrics", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "tablets", + .Description = "return tablets", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "system_tablets", + .Description = "return system tablets", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "offload_merge", + .Description = "use offload merge", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "storage", + .Description = "return storage info", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "nodes", + .Description = "return nodes info", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "\"Returns information about tenants\""; - } -}; - -} } diff --git a/ydb/core/viewer/json_tenants.h b/ydb/core/viewer/viewer_tenants.h similarity index 62% rename from ydb/core/viewer/json_tenants.h rename to ydb/core/viewer/viewer_tenants.h index e15ebdfcc152..d0c2baa1c637 100644 --- a/ydb/core/viewer/json_tenants.h +++ b/ydb/core/viewer/viewer_tenants.h @@ -1,22 +1,15 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include "json_handlers.h" #include "json_pipe_req.h" #include "wb_aggregate.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; -class TJsonTenants : public TViewerPipeClient { - using TBase = TViewerPipeClient; +class TJsonTenants : public TViewerPipeClient { + using TThis = TJsonTenants; + using TBase = TViewerPipeClient; IViewer* Viewer; NKikimrViewer::TTenants Result; NMon::TEvHttpInfo::TPtr Event; @@ -26,16 +19,12 @@ class TJsonTenants : public TViewerPipeClient { THashMap TenantIndex; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TJsonTenants(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) : Viewer(viewer) , Event(ev) {} - void Bootstrap() { + void Bootstrap() override { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault(params.Get("enums"), true); JsonSettings.UI64AsString = !FromStringWithDefault(params.Get("ui64"), false); @@ -87,7 +76,7 @@ class TJsonTenants : public TViewerPipeClient { RequestDone(); } - void ReplyAndPassAway() { + void ReplyAndPassAway() override { TStringStream json; TProtoToJson::ProtoToJson(json, Result, JsonSettings); Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), json.Str()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); @@ -98,57 +87,38 @@ class TJsonTenants : public TViewerPipeClient { Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); PassAway(); } -}; -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Tenant info (brief)", + .Description = "Returns list of tenants", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "state", + .Description = "return tenant state", + .Type = "boolean", + .Default = "true", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: state - in: query - description: return tenant state - required: false - type: boolean - default: true - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - )___"); - } -}; - -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Tenant info (brief)"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns list of tenants"; - } -}; - -} } diff --git a/ydb/core/viewer/json_topicinfo.h b/ydb/core/viewer/viewer_topicinfo.h similarity index 64% rename from ydb/core/viewer/json_topicinfo.h rename to ydb/core/viewer/viewer_topicinfo.h index d5880859ff16..9172624d667a 100644 --- a/ydb/core/viewer/json_topicinfo.h +++ b/ydb/core/viewer/viewer_topicinfo.h @@ -1,14 +1,10 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "json_handlers.h" #include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -90,69 +86,51 @@ class TJsonTopicInfo : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; - -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { - return TProtoToYaml::ProtoToYamlSchema(); - } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return YAML::Load(R"___( - - name: path - in: query - description: schema path - required: true - type: string - - name: client - in: query - description: client name - required: false - type: string - default: total - - name: enums - in: query - description: convert enums to strings - required: false - type: boolean - - name: all - in: query - description: return all topics and all clients - required: false - type: boolean - default: false - - name: ui64 - in: query - description: return ui64 as number - required: false - type: boolean - - name: timeout - in: query - description: timeout in ms - required: false - type: integer - default: 10000 - )___"); + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Topic information", + .Description = "Information about topic", + }); + yaml.AddParameter({ + .Name = "path", + .Description = "schema path", + .Type = "string", + .Required = true, + }); + yaml.AddParameter({ + .Name = "client", + .Description = "client name", + .Type = "string", + .Default = "total", + }); + yaml.AddParameter({ + .Name = "enums", + .Description = "convert enums to strings", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "all", + .Description = "return all topics and all clients", + .Type = "boolean", + .Default = "false", + }); + yaml.AddParameter({ + .Name = "ui64", + .Description = "return ui64 as number", + .Type = "boolean", + }); + yaml.AddParameter({ + .Name = "timeout", + .Description = "timeout in ms", + .Type = "integer", + .Default = "10000", + }); + yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Topic information"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Information about topic"; - } -}; - -} } diff --git a/ydb/core/viewer/viewer_ut.cpp b/ydb/core/viewer/viewer_ut.cpp index 3f4f3e73277f..20d7c731b81b 100644 --- a/ydb/core/viewer/viewer_ut.cpp +++ b/ydb/core/viewer/viewer_ut.cpp @@ -10,14 +10,14 @@ #include #include #include -#include "json_handlers.h" -#include "json_tabletinfo.h" -#include "json_vdiskinfo.h" -#include "json_pdiskinfo.h" +#include "viewer_tabletinfo.h" +#include "viewer_vdiskinfo.h" +#include "viewer_pdiskinfo.h" #include "query_autocomplete_helper.h" #include #include +#include #include #include #include @@ -26,6 +26,7 @@ #include #include +#include using namespace NKikimr; using namespace NViewer; @@ -310,6 +311,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(2) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -376,6 +378,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(nodesTotal) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -410,7 +413,11 @@ Y_UNIT_TEST_SUITE(Viewer) { case TEvTxProxySchemeCache::EvNavigateKeySetResult: { auto *x = reinterpret_cast(&ev); auto &domain = (*x)->Get()->Request->ResultSet.begin()->DomainInfo; - domain->Params.SetHive(1); + + // Event can be generated by workload manager, it is ok for not found response without domain + if (domain) { + domain->Params.SetHive(1); + } break; } case TEvHive::EvResponseHiveDomainStats: { @@ -460,7 +467,8 @@ Y_UNIT_TEST_SUITE(Viewer) { settings.InitKikimrRunConfig() .SetNodeCount(1) .SetUseRealThreads(false) - .SetDomainName("Root"); + .SetDomainName("Root") + .SetUseSectorMap(true); TServer server(settings); server.EnableGRpc(grpcPort); TClient client(settings); @@ -525,7 +533,8 @@ Y_UNIT_TEST_SUITE(Viewer) { settings.InitKikimrRunConfig() .SetNodeCount(1) .SetUseRealThreads(false) - .SetDomainName("Root"); + .SetDomainName("Root") + .SetUseSectorMap(true); TServer server(settings); server.EnableGRpc(grpcPort); TClient client(settings); @@ -599,16 +608,19 @@ Y_UNIT_TEST_SUITE(Viewer) { entry.Status = TSchemeCacheNavigate::EStatus::Ok; entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; entry.DomainInfo = MakeIntrusive(SERVERLESS_DOMAIN_KEY, SHARED_DOMAIN_KEY); + entry.Path = {"Root", "serverless"}; } else if (path == "/Root/shared" || entry.TableId.PathId == SHARED_DOMAIN_KEY) { entry.Status = TSchemeCacheNavigate::EStatus::Ok; entry.Kind = TSchemeCacheNavigate::EKind::KindExtSubdomain; entry.DomainInfo = MakeIntrusive(SHARED_DOMAIN_KEY, SHARED_DOMAIN_KEY); + entry.Path = {"Root", "shared"}; auto domains = runtime.GetAppData().DomainsInfo; entry.DomainInfo->Params.SetHive(domains->GetHive()); } else if (path == "/Root/serverless/users" || entry.TableId.PathId == SERVERLESS_TABLE) { entry.Status = TSchemeCacheNavigate::EStatus::Ok; entry.Kind = TSchemeCacheNavigate::EKind::KindTable; entry.DomainInfo = MakeIntrusive(SERVERLESS_DOMAIN_KEY, SHARED_DOMAIN_KEY); + entry.Path = {"Root", "serverless", "users"}; auto dirEntryInfo = MakeIntrusive(); dirEntryInfo->Info.SetSchemeshardId(SERVERLESS_TABLE.OwnerId); dirEntryInfo->Info.SetPathId(SERVERLESS_TABLE.LocalPathId); @@ -678,6 +690,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetDynamicNodeCount(1) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -689,11 +702,11 @@ Y_UNIT_TEST_SUITE(Viewer) { TAutoPtr handle; THttpRequest httpReq(HTTP_METHOD_GET); - httpReq.CgiParameters.emplace("path", "/Root/serverless"); + httpReq.CgiParameters.emplace("database", "/Root/serverless"); httpReq.CgiParameters.emplace("tablets", "true"); httpReq.CgiParameters.emplace("enums", "true"); httpReq.CgiParameters.emplace("sort", ""); - httpReq.CgiParameters.emplace("type", "any"); + httpReq.CgiParameters.emplace("direct", "1"); auto page = MakeHolder("viewer", "title"); TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); auto request = MakeHolder(monReq); @@ -744,8 +757,8 @@ Y_UNIT_TEST_SUITE(Viewer) { 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"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("TotalNodes"), "1"); + UNIT_ASSERT_VALUES_EQUAL(json.GetMap().at("FoundNodes"), "1"); } Y_UNIT_TEST(ServerlessWithExclusiveNodes) @@ -758,6 +771,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetDynamicNodeCount(2) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -769,11 +783,8 @@ Y_UNIT_TEST_SUITE(Viewer) { TAutoPtr handle; THttpRequest httpReq(HTTP_METHOD_GET); - httpReq.CgiParameters.emplace("path", "/Root/serverless"); - httpReq.CgiParameters.emplace("tablets", "true"); - httpReq.CgiParameters.emplace("enums", "true"); - httpReq.CgiParameters.emplace("sort", ""); - httpReq.CgiParameters.emplace("type", "any"); + httpReq.CgiParameters.emplace("database", "/Root/serverless"); + httpReq.CgiParameters.emplace("direct", "1"); auto page = MakeHolder("viewer", "title"); TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); auto request = MakeHolder(monReq); @@ -843,6 +854,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetDynamicNodeCount(2) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -854,11 +866,8 @@ Y_UNIT_TEST_SUITE(Viewer) { TAutoPtr handle; THttpRequest httpReq(HTTP_METHOD_GET); - httpReq.CgiParameters.emplace("path", "/Root/shared"); - httpReq.CgiParameters.emplace("tablets", "true"); - httpReq.CgiParameters.emplace("enums", "true"); - httpReq.CgiParameters.emplace("sort", ""); - httpReq.CgiParameters.emplace("type", "any"); + httpReq.CgiParameters.emplace("database", "/Root/shared"); + httpReq.CgiParameters.emplace("direct", "1"); auto page = MakeHolder("viewer", "title"); TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/nodes", nullptr); auto request = MakeHolder(monReq); @@ -928,6 +937,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetDynamicNodeCount(3) .SetUseRealThreads(false) .SetDomainName("Root") + .SetUseSectorMap(true) .InitKikimrRunConfig(); TServer server(settings); server.EnableGRpc(grpcPort); @@ -939,11 +949,10 @@ Y_UNIT_TEST_SUITE(Viewer) { TAutoPtr handle; THttpRequest httpReq(HTTP_METHOD_GET); + httpReq.CgiParameters.emplace("database", "/Root/serverless"); httpReq.CgiParameters.emplace("path", "/Root/serverless/users"); + httpReq.CgiParameters.emplace("direct", "1"); 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); @@ -1035,12 +1044,11 @@ Y_UNIT_TEST_SUITE(Viewer) { TVector DifferentWordsDictionary = { "/orders", "/peoples", "/OrdinaryScheduleTables" }; void FuzzySearcherTest(TVector& dictionary, TString search, ui32 limit, TVector expectations) { - auto fuzzy = FuzzySearcher(dictionary); - auto result = fuzzy.Search(search, limit); + auto result = FuzzySearcher::Search(dictionary, search, limit); UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size()); for (ui32 i = 0; i < expectations.size(); i++) { - UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]); + UNIT_ASSERT_VALUES_EQUAL(expectations[i], *result[i]); } } @@ -1083,7 +1091,8 @@ Y_UNIT_TEST_SUITE(Viewer) { settings.InitKikimrRunConfig() .SetNodeCount(1) .SetUseRealThreads(false) - .SetDomainName("Root"); + .SetDomainName("Root") + .SetUseSectorMap(true); TServer server(settings); server.EnableGRpc(grpcPort); TClient client(settings); @@ -1303,8 +1312,11 @@ Y_UNIT_TEST_SUITE(Viewer) { JsonAutocompleteTest(HTTP_METHOD_GET, value, "nam", "/Root/Database", {"orders", "products"}); VerifyJsonAutocompleteSuccess(value, { "name", + "name", + "id", "id", "description", + "description", }); } @@ -1313,8 +1325,11 @@ Y_UNIT_TEST_SUITE(Viewer) { JsonAutocompleteTest(HTTP_METHOD_POST, value, "nam", "/Root/Database", {"orders", "products"}); VerifyJsonAutocompleteSuccess(value, { "name", + "name", + "id", "id", "description", + "description", }); } @@ -1396,7 +1411,8 @@ Y_UNIT_TEST_SUITE(Viewer) { settings.InitKikimrRunConfig() .SetNodeCount(9) .SetUseRealThreads(false) - .SetDomainName("Root"); + .SetDomainName("Root") + .SetUseSectorMap(true); TServer server(settings); server.EnableGRpc(grpcPort); TClient client(settings); @@ -1619,6 +1635,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(1) .SetUseRealThreads(true) .SetDomainName("Root") + .SetUseSectorMap(true) .SetMonitoringPortOffset(monPort, true); TServer server(settings); @@ -1651,6 +1668,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(1) .SetUseRealThreads(true) .SetDomainName("Root") + .SetUseSectorMap(true) .SetMonitoringPortOffset(monPort, true); TServer server(settings); @@ -1685,6 +1703,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(1) .SetUseRealThreads(true) .SetDomainName("Root") + .SetUseSectorMap(true) .SetMonitoringPortOffset(monPort, true); TServer server(settings); @@ -1735,6 +1754,7 @@ Y_UNIT_TEST_SUITE(Viewer) { .SetNodeCount(1) .SetUseRealThreads(true) .SetDomainName("Root") + .SetUseSectorMap(true) .SetMonitoringPortOffset(monPort, true); // authorization is implemented only in async mon auto& securityConfig = *settings.AppConfig->MutableDomainsConfig()->MutableSecurityConfig(); @@ -1770,7 +1790,7 @@ Y_UNIT_TEST_SUITE(Viewer) { "database": "/Root", "action": "execute-script", "syntax": "yql_v1", - "stats": "none" + "stats": "profile" })json"; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost("/viewer/query?timeout=600000&base64=false&schema=modern", requestBody, &responseStream, headers); const TString response = responseStream.ReadAll(); @@ -1780,4 +1800,139 @@ Y_UNIT_TEST_SUITE(Viewer) { UNIT_ASSERT_VALUES_EQUAL_C(ticketParser->AuthorizeTicketRequests, 1, response); UNIT_ASSERT_VALUES_EQUAL_C(ticketParser->AuthorizeTicketSuccesses, 1, response); } + + Y_UNIT_TEST(SimpleFeatureFlags) { + TPortManager tp; + ui16 port = tp.GetPort(2134); + ui16 grpcPort = tp.GetPort(2135); + ui16 monPort = tp.GetPort(8765); + auto settings = TServerSettings(port); + + settings.InitKikimrRunConfig() + .SetNodeCount(1) + .SetUseRealThreads(true) + .SetDomainName("Root") + .SetUseSectorMap(true) + .SetMonitoringPortOffset(monPort, true); + + TServer server(settings); + server.EnableGRpc(grpcPort); + TClient client(settings); + + TKeepAliveHttpClient httpClient("localhost", monPort); + TStringStream responseStream; + TKeepAliveHttpClient::THeaders headers; + headers["Accept"] = "application/json"; + const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet("/viewer/feature_flags", &responseStream, headers); + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response); + NJson::TJsonReaderConfig jsonCfg; + NJson::TJsonValue json; + NJson::ReadJsonTree(response, &jsonCfg, &json, /* throwOnError = */ true); + auto resultSets = json["Databases"].GetArray(); + UNIT_ASSERT_EQUAL_C(1, resultSets.size(), response); + } + + static const ui32 ROWS_N = 15; + static const ui32 ROWS_LIMIT = 5; + + TString PostExecuteScript(TKeepAliveHttpClient& httpClient, TString query) { + TStringStream requestBody; + requestBody + << "{ \"database\": \"/Root\"," + << " \"script_content\": {" + << " \"text\": \"" << query << "\"}," + << " \"exec_mode\": \"EXEC_MODE_EXECUTE\" }"; + TStringStream responseStream; + TKeepAliveHttpClient::THeaders headers; + headers["Content-Type"] = "application/json"; + headers["Authorization"] = "test_ydb_token"; + const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost(TStringBuilder() + << "/query/script/execute?timeout=600000" + << "&database=%2FRoot", requestBody.Str(), &responseStream, headers); + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response); + return response; + } + + TString GetOperation(TKeepAliveHttpClient& httpClient, TString id) { + TStringStream requestBody; + TStringStream responseStream; + TKeepAliveHttpClient::THeaders headers; + headers["Content-Type"] = "application/json"; + headers["Authorization"] = "test_ydb_token"; + id = std::regex_replace(id.c_str(), std::regex("/"), "%2F"); + const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet(TStringBuilder() + << "/operation/get?timeout=600000&id=" << id + << "&database=%2FRoot", &responseStream, headers); + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response); + return response; + } + + TString GetFetchScript(TKeepAliveHttpClient& httpClient, TString id) { + TStringStream requestBody; + TStringStream responseStream; + TKeepAliveHttpClient::THeaders headers; + headers["Content-Type"] = "application/json"; + headers["Authorization"] = "test_ydb_token"; + id = std::regex_replace(id.c_str(), std::regex("/"), "%2F"); + const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet(TStringBuilder() + << "/query/script/fetch?timeout=600000&operation_id=" << id + << "&database=%2FRoot" + << "&rows_limit=" << ROWS_LIMIT, &responseStream, headers); + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response); + return response; + } + + Y_UNIT_TEST(QueryExecuteScript) { + TPortManager tp; + ui16 port = tp.GetPort(2134); + ui16 grpcPort = tp.GetPort(2135); + ui16 monPort = tp.GetPort(8765); + auto settings = TServerSettings(port); + settings.InitKikimrRunConfig() + .SetNodeCount(1) + .SetUseRealThreads(true) + .SetDomainName("Root") + .SetUseSectorMap(true) + .SetMonitoringPortOffset(monPort, true); + + TServer server(settings); + server.EnableGRpc(grpcPort); + TClient client(settings); + client.InitRootScheme(); + + TTestActorRuntime& runtime = *server.GetRuntime(); + runtime.SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE); + + TKeepAliveHttpClient httpClient("localhost", monPort); + + PostQuery(httpClient, "CREATE TABLE `/Root/Test` (Key Uint64, Value String, PRIMARY KEY (Key));", "execute-query"); + for (ui32 i = 1; i <= ROWS_N; ++i) { + PostQuery(httpClient, TStringBuilder() << "INSERT INTO `/Root/Test` (Key, Value) VALUES (" << i << ", 'testvalue');", "execute-query"); + } + + NJson::TJsonReaderConfig jsonCfg; + NJson::TJsonValue json; + + TString response = PostExecuteScript(httpClient, "SELECT * FROM `/Root/Test`;"); + NJson::ReadJsonTree(response, &jsonCfg, &json, /* throwOnError = */ true); + UNIT_ASSERT_EQUAL_C(json["status"].GetString(), "SUCCESS", response); + TString id = json["id"].GetString(); + + Sleep(TDuration::MilliSeconds(1000)); + + response = GetOperation(httpClient, id); + NJson::ReadJsonTree(response, &jsonCfg, &json, /* throwOnError = */ true); + UNIT_ASSERT_EQUAL_C(json["issues"].GetArray().size(), 0, response); + + response = GetFetchScript(httpClient, id); + NJson::ReadJsonTree(response, &jsonCfg, &json, /* throwOnError = */ true); + UNIT_ASSERT_EQUAL_C(json["status"].GetString(), "SUCCESS", response); + auto rows = json["result_set"].GetMap().at("rows").GetArray(); + UNIT_ASSERT_EQUAL_C(rows.size(), ROWS_LIMIT, response); + } + } diff --git a/ydb/core/viewer/viewer_vdiskinfo.h b/ydb/core/viewer/viewer_vdiskinfo.h new file mode 100644 index 000000000000..e80003300cec --- /dev/null +++ b/ydb/core/viewer/viewer_vdiskinfo.h @@ -0,0 +1,39 @@ +#pragma once +#include "json_wb_req.h" +#include "viewer_helper.h" + +namespace NKikimr::NViewer { + +template <> +struct TWhiteboardInfo { + using TResponseEventType = TEvWhiteboard::TEvVDiskStateResponse; + using TResponseType = NKikimrWhiteboard::TEvVDiskStateResponse; + using TElementType = NKikimrWhiteboard::TVDiskStateInfo; + using TElementKeyType = NKikimrBlobStorage::TVDiskID; + + static constexpr bool StaticNodesOnly = true; + + static ::google::protobuf::RepeatedPtrField& GetElementsField(TResponseType& response) { + return *response.MutableVDiskStateInfo(); + } + + static const NKikimrBlobStorage::TVDiskID& GetElementKey(const TElementType& type) { + return type.GetVDiskId(); + } + + static TString GetDefaultMergeField() { + return "VDiskId"; + } + + static void MergeResponses(TResponseType& result, TMap& responses, const TString& fields = GetDefaultMergeField()) { + if (fields == GetDefaultMergeField()) { + TWhiteboardMerger::MergeResponsesElementKey(result, responses); + } else { + TWhiteboardMerger::MergeResponses(result, responses, fields); + } + } +}; + +using TJsonVDiskInfo = TJsonWhiteboardRequest; + +} diff --git a/ydb/core/viewer/json_whoami.h b/ydb/core/viewer/viewer_whoami.h similarity index 82% rename from ydb/core/viewer/json_whoami.h rename to ydb/core/viewer/viewer_whoami.h index 36e3715268fd..cf1a6eee7927 100644 --- a/ydb/core/viewer/json_whoami.h +++ b/ydb/core/viewer/viewer_whoami.h @@ -1,18 +1,13 @@ #pragma once -#include -#include +#include "json_handlers.h" +#include "viewer.h" +#include #include #include -#include -#include -#include -#include -#include -#include -#include "viewer.h" +#include +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; @@ -80,11 +75,8 @@ class TJsonWhoAmI : public TActorBootstrapped { ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get()), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } -}; -template <> -struct TJsonRequestSchema { - static YAML::Node GetSchema() { + static YAML::Node GetSwaggerSchema() { return YAML::Load(R"___( type: object title: WhoAmI @@ -114,28 +106,17 @@ struct TJsonRequestSchema { description: Is user allowed to do unrestricted changes in the system )___"); } -}; -template <> -struct TJsonRequestParameters { - static YAML::Node GetParameters() { - return {}; + static YAML::Node GetSwagger() { + TSimpleYamlBuilder yaml({ + .Method = "get", + .Tag = "viewer", + .Summary = "Information about current user", + .Description = "Returns information about user token", + }); + yaml.SetResponseSchema(GetSwaggerSchema()); + return yaml; } }; -template <> -struct TJsonRequestSummary { - static TString GetSummary() { - return "Information about current user"; - } -}; - -template <> -struct TJsonRequestDescription { - static TString GetDescription() { - return "Returns information about user token"; - } -}; - -} } diff --git a/ydb/core/viewer/wb_aggregate.cpp b/ydb/core/viewer/wb_aggregate.cpp index 7f3d56d64b76..45e23a53a622 100644 --- a/ydb/core/viewer/wb_aggregate.cpp +++ b/ydb/core/viewer/wb_aggregate.cpp @@ -1,7 +1,6 @@ #include "wb_aggregate.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { void AggregateMessage(::google::protobuf::Message& protoTo, const ::google::protobuf::Message& protoFrom) { const Reflection& reflectionFrom = *protoFrom.GetReflection(); @@ -177,4 +176,3 @@ void AggregateMessage(::google::protobuf::Message& protoTo, const ::google::prot } } -} diff --git a/ydb/core/viewer/wb_aggregate.h b/ydb/core/viewer/wb_aggregate.h index 7d09f1565f6c..c9b589f32115 100644 --- a/ydb/core/viewer/wb_aggregate.h +++ b/ydb/core/viewer/wb_aggregate.h @@ -1,19 +1,17 @@ #pragma once -#include #include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; using namespace ::google::protobuf; -template +template struct TWhiteboardInfo; void AggregateMessage(::google::protobuf::Message& protoTo, const ::google::protobuf::Message& protoFrom); -template +template class TWhiteboardAggregator { public: using TResponseType = ResponseType; @@ -46,4 +44,3 @@ THolder AggregateWhiteboardResponses(TMap i32 TFieldProtoValueExtractor::ExtractValue(const Reflection& reflection, const Message& element) const { @@ -97,4 +96,3 @@ TMessageValue TFieldProtoValueExtractor::ExtractValue(const Refle } } -} diff --git a/ydb/core/viewer/wb_filter.h b/ydb/core/viewer/wb_filter.h index 64926899e70c..8222c58366c1 100644 --- a/ydb/core/viewer/wb_filter.h +++ b/ydb/core/viewer/wb_filter.h @@ -1,15 +1,14 @@ #pragma once +#include #include #include -#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; using namespace ::google::protobuf; -template +template struct TWhiteboardInfo; struct TEnumValue { @@ -552,4 +551,3 @@ void FilterWhiteboardResponses(ResponseType& response, const TString& filters) { } } -} diff --git a/ydb/core/viewer/wb_group.h b/ydb/core/viewer/wb_group.h index 731c5db9f4b1..2250a45daaf7 100644 --- a/ydb/core/viewer/wb_group.h +++ b/ydb/core/viewer/wb_group.h @@ -1,15 +1,14 @@ #pragma once +#include #include #include -#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; using namespace ::google::protobuf; -template +template struct TWhiteboardInfo; template @@ -304,4 +303,3 @@ void GroupWhiteboardResponses(ResponseType& response, const TString& fields, boo } } -} diff --git a/ydb/core/viewer/wb_merge.cpp b/ydb/core/viewer/wb_merge.cpp index cca5dc1d9deb..cc092ad4540a 100644 --- a/ydb/core/viewer/wb_merge.cpp +++ b/ydb/core/viewer/wb_merge.cpp @@ -1,7 +1,6 @@ #include "wb_merge.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; using namespace ::google::protobuf; @@ -216,4 +215,3 @@ void TWhiteboardMergerBase::ProtoMerge(google::protobuf::Message& protoTo, const } } -} diff --git a/ydb/core/viewer/wb_merge.h b/ydb/core/viewer/wb_merge.h index e9912de2ae6e..64d37d0bfd10 100644 --- a/ydb/core/viewer/wb_merge.h +++ b/ydb/core/viewer/wb_merge.h @@ -1,23 +1,21 @@ #pragma once +#include "wb_filter.h" +#include "wb_group.h" #include #include #include -#include "wb_filter.h" -#include "wb_group.h" -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NNodeWhiteboard; using namespace ::google::protobuf; -struct TWhiteboardDefaultInfo { -}; +struct TWhiteboardDefaultInfo {}; -template +template struct TWhiteboardInfo; -template +template struct TWhiteboardMergerComparator { bool operator ()(const ResponseType& a, const ResponseType& b) const { return a.GetChangeTime() < b.GetChangeTime(); @@ -304,4 +302,3 @@ void MergeWhiteboardResponses(ResponseType& result, TMap& re } } -} diff --git a/ydb/core/viewer/wb_req.h b/ydb/core/viewer/wb_req.h index 63b899dab5ee..4a9800bb276d 100644 --- a/ydb/core/viewer/wb_req.h +++ b/ydb/core/viewer/wb_req.h @@ -1,25 +1,13 @@ #pragma once - -#pragma once -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include "viewer.h" #include "json_pipe_req.h" -#include "wb_merge.h" -#include "wb_group.h" -#include "wb_filter.h" #include "log.h" +#include "viewer.h" +#include "wb_filter.h" +#include "wb_group.h" +#include "wb_merge.h" +#include -namespace NKikimr { -namespace NViewer { +namespace NKikimr::NViewer { using namespace NActors; using namespace NNodeWhiteboard; @@ -41,33 +29,33 @@ struct TEvPrivate { }; }; -template -class TWhiteboardRequest : public TViewerPipeClient { +template +class TWhiteboardRequest : public TViewerPipeClient { protected: - using TThis = TWhiteboardRequest; - using TBase = TViewerPipeClient; + using TThis = TWhiteboardRequest; + using TBase = TViewerPipeClient; using TResponseType = typename TResponseEventType::ProtoRecordType; TRequestSettings RequestSettings; THolder Request; - std::unordered_set NodeIds; - TMap PerNodeStateInfo; // map instead of unordered_map only for sorting reason - std::unordered_map NodeErrors; + std::unordered_map> NodeResponses; TInstant NodesRequestedTime; std::unordered_map NodeRetries; TString LogPrefix; public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType() { - return NKikimrServices::TActivity::VIEWER_HANDLER; - } - TString GetLogPrefix() { return LogPrefix; } - TWhiteboardRequest() = default; + TWhiteboardRequest(NWilson::TTraceId traceId) + : TBase(std::move(traceId)) + {} + + TWhiteboardRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + : TBase(viewer, ev) + {} - THolder BuildRequest() { + virtual THolder BuildRequest() { THolder request = MakeHolder(); constexpr bool hasFormat = requires(const TRequestEventType* r) {r->Record.GetFormat();}; if constexpr (hasFormat) { @@ -75,6 +63,14 @@ class TWhiteboardRequest : public TViewerPipeClient { request->Record.SetFormat(RequestSettings.Format); } } + + constexpr bool hasFieldsRequired = requires(const TRequestEventType* r) {r->Record.GetFieldsRequired();}; + if constexpr (hasFieldsRequired) { + if (!RequestSettings.FieldsRequired.empty()) { + request->Record.MutableFieldsRequired()->Add(RequestSettings.FieldsRequired.begin(), RequestSettings.FieldsRequired.end()); + } + } + if (RequestSettings.ChangedSince != 0) { request->Record.SetChangedSince(RequestSettings.ChangedSince); } @@ -88,11 +84,7 @@ class TWhiteboardRequest : public TViewerPipeClient { } void SendNodeRequestToWhiteboard(TNodeId nodeId) { - TActorId whiteboardServiceId = MakeNodeWhiteboardServiceId(nodeId); - THolder request = CloneRequest(); - BLOG_TRACE("Sent WhiteboardRequest to " << nodeId << " Request: " << request->Record.ShortDebugString()); - TBase::SendRequest(whiteboardServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, nodeId); - NodeIds.insert(nodeId); + NodeResponses[nodeId] = MakeWhiteboardRequest(nodeId, CloneRequest().Release()); } void SendNodeRequest(const std::vector nodeIds) { @@ -101,14 +93,7 @@ class TWhiteboardRequest : public TViewerPipeClient { } } - void PassAway() override { - for (const TNodeId nodeId : NodeIds) { - TBase::Send(TActivationContext::InterconnectProxy(nodeId), new TEvents::TEvUnsubscribe()); - } - TBase::PassAway(); - } - - virtual void Bootstrap() { + void Bootstrap() override { std::replace(RequestSettings.FilterNodeIds.begin(), RequestSettings.FilterNodeIds.end(), (ui32)0, @@ -172,7 +157,7 @@ class TWhiteboardRequest : public TViewerPipeClient { if (TBase::Requests > 0) { TBase::Become(&TThis::StateRequestedNodeInfo); } else { - static_cast(this)->ReplyAndPassAway(); + ReplyAndPassAway(); } } @@ -208,7 +193,7 @@ class TWhiteboardRequest : public TViewerPipeClient { if (TBase::Requests > 0) { TBase::Become(&TThis::StateRequestedNodeInfo); } else { - static_cast(this)->ReplyAndPassAway(); + ReplyAndPassAway(); } } @@ -225,16 +210,9 @@ class TWhiteboardRequest : public TViewerPipeClient { void Undelivered(TEvents::TEvUndelivered::TPtr& ev) { static const TString error = "Undelivered"; TNodeId nodeId = ev.Get()->Cookie; - if (PerNodeStateInfo.emplace(nodeId, TResponseType{}).second) { - NodeErrors[nodeId] = error; + if (NodeResponses[nodeId].Error(error)) { if (!RetryRequest(nodeId)) { - TBase::RequestDone(); - } - } else { - if (NodeErrors[nodeId] == error) { - if (!RetryRequest(nodeId)) { - TBase::RequestDone(); - } + RequestDone(); } } } @@ -242,16 +220,9 @@ class TWhiteboardRequest : public TViewerPipeClient { void Disconnected(TEvInterconnect::TEvNodeDisconnected::TPtr& ev) { static const TString error = "Node disconnected"; TNodeId nodeId = ev->Get()->NodeId; - if (PerNodeStateInfo.emplace(nodeId, TResponseType{}).second) { - NodeErrors[nodeId] = error; + if (NodeResponses[nodeId].Error(error)) { if (!RetryRequest(nodeId)) { - TBase::RequestDone(); - } - } else { - if (NodeErrors[nodeId] == error) { - if (!RetryRequest(nodeId)) { - TBase::RequestDone(); - } + RequestDone(); } } } @@ -276,29 +247,35 @@ class TWhiteboardRequest : public TViewerPipeClient { } } - template - void OnRecordReceived(ResponseRecordType& record, TNodeId nodeId) { - record.SetResponseDuration((AppData()->TimeProvider->Now() - NodesRequestedTime).MicroSeconds()); - BLOG_TRACE("Received " << typeid(TResponseEventType).name() << " from " << nodeId << GetResponseDuration(record) << GetProcessDuration(record)); - } - void HandleNodeInfo(typename TResponseEventType::TPtr& ev) { ui64 nodeId = ev.Get()->Cookie; - OnRecordReceived(ev->Get()->Record, nodeId); - PerNodeStateInfo[nodeId] = std::move(ev->Get()->Record); - NodeErrors.erase(nodeId); + ev->Get()->Record.SetResponseDuration((AppData()->TimeProvider->Now() - NodesRequestedTime).MicroSeconds()); + NodeResponses[nodeId].Set(std::move(ev)); + //PerNodeStateInfo[nodeId] = std::move(ev->Get()->Record); TBase::RequestDone(); } void HandleRetryNode(TEvPrivate::TEvRetryNodeRequest::TPtr& ev) { + NodeResponses.erase(ev->Get()->NodeId); SendNodeRequest({ev->Get()->NodeId}); TBase::RequestDone(); // previous, failed one } void HandleTimeout() { - static_cast(this)->ReplyAndPassAway(); + ReplyAndPassAway(); + } + + TMap GetPerNodeStateInfo() { // map instead of unordered_map only for sorting reason + TMap perNodeStateInfo; + for (const auto& [nodeId, response] : NodeResponses) { + if (response.IsOk()) { + perNodeStateInfo.emplace(nodeId, std::move(response->Record)); + } else { + perNodeStateInfo[nodeId]; // empty data for failed requests + } + } + return perNodeStateInfo; } }; } -} diff --git a/ydb/core/viewer/ya.make b/ydb/core/viewer/ya.make index 841de0ea26e4..91c027e74ac6 100644 --- a/ydb/core/viewer/ya.make +++ b/ydb/core/viewer/ya.make @@ -4,75 +4,99 @@ RECURSE_FOR_TESTS( LIBRARY() +IF(NOT OS_WINDOWS) +IF(BUILD_TYPE == RELEASE OR BUILD_TYPE == RELWITHDEBINFO) + CXXFLAGS(-Oz) +ENDIF() +ENDIF() + SRCS( browse_db.h - browse_pq.h + browse_events.h browse.h - check_access.h + browse_pq.h counters_hosts.h - json_acl.h - json_autocomplete.h - json_blobindexstat.h - json_browse.h - json_bscontrollerinfo.h - json_bsgroupinfo.h - json_cluster.h - json_compute.h - json_config.h - json_content.h - json_counters.h - json_describe.h - json_describe_consumer.h - json_describe_topic.h - json_local_rpc.h - json_getblob.h - json_graph.h + healthcheck_record.h + json_handlers.cpp + json_handlers.h + json_handlers_browse.cpp json_handlers_operation.cpp + json_handlers_query.cpp json_handlers_pdisk.cpp json_handlers_scheme.cpp + json_handlers_storage.cpp json_handlers_vdisk.cpp json_handlers_viewer.cpp - json_healthcheck.h - json_hiveinfo.h - json_hotkeys.h - json_labeledcounters.h - json_metainfo.h - json_netinfo.h - json_nodeinfo.h - json_nodelist.h - json_nodes.h - json_pdiskinfo.h - json_query.h - json_query_old.h - json_render.h - json_storage.h - json_sysinfo.h - json_tabletcounters.h - json_tabletinfo.h - json_tenants.h - json_tenantinfo.h - json_topicinfo.h - json_pqconsumerinfo.h + json_handlers_pq.cpp + json_local_rpc.h + json_pipe_req.cpp + json_pipe_req.h + json_storage_base.h json_vdisk_req.h - json_vdisk_evict.h - json_vdiskinfo.h - json_vdiskstat.h + json_wb_req.cpp json_wb_req.h - json_whoami.h log.h operation_cancel.h operation_forget.h operation_get.h operation_list.h pdisk_info.h + pdisk_restart.h pdisk_status.h - scheme_directory.h query_autocomplete_helper.h + scheme_directory.h + storage_groups.h + vdisk_blobindexstat.h + vdisk_evict.h + vdisk_getblob.h + vdisk_vdiskstat.h + viewer_acl.h + viewer_autocomplete.h + viewer_browse.h + viewer_bscontrollerinfo.h + viewer_bsgroupinfo.h + viewer_capabilities.h + viewer_check_access.h + viewer_cluster.h + viewer_compute.h + viewer_config.h + viewer_content.h + viewer_counters.h + viewer_describe_consumer.h + viewer_describe.h + viewer_describe_topic.h + viewer_feature_flags.h + viewer_graph.h + viewer_healthcheck.h + viewer_helper.h + viewer_hiveinfo.h + viewer_hivestats.h + viewer_hotkeys.h + viewer_labeled_counters.h + viewer_metainfo.h + viewer_netinfo.h + viewer_nodeinfo.h + viewer_nodelist.h + viewer_nodes.h + viewer_pdiskinfo.h + viewer_pqconsumerinfo.h + viewer_query.h + viewer_query_old.h + viewer_render.h viewer_request.cpp viewer_request.h - viewer.cpp + viewer_storage.h + viewer_storage_usage.h + viewer_sysinfo.h + viewer_tabletcounters.h + viewer_tabletinfo.h + viewer_tenantinfo.h + viewer_tenants.h + viewer_topicinfo.h + viewer_vdiskinfo.h + viewer_whoami.h viewer.h - viewer_probes.cpp + viewer.cpp wb_aggregate.cpp wb_aggregate.h wb_filter.cpp @@ -80,6 +104,7 @@ SRCS( wb_group.h wb_merge.cpp wb_merge.h + wb_req.h ) IF (NOT EXPORT_CMAKE) @@ -542,6 +567,7 @@ PEERDIR( ydb/core/viewer/yaml ydb/core/viewer/protos ydb/library/persqueue/topic_parser + ydb/library/yaml_config ydb/public/api/protos ydb/public/lib/deprecated/kicli ydb/public/lib/json_value diff --git a/ydb/core/viewer/yaml/yaml.cpp b/ydb/core/viewer/yaml/yaml.cpp index a23afaef2870..c2175e12858d 100644 --- a/ydb/core/viewer/yaml/yaml.cpp +++ b/ydb/core/viewer/yaml/yaml.cpp @@ -47,6 +47,9 @@ YAML::Node TProtoToYaml::ProtoToYamlSchema(const ::google::protobuf::Descriptor* int oneofFields = descriptor->oneof_decl_count(); for (int idx = 0; idx < oneofFields; ++idx) { const OneofDescriptor* fieldDescriptor = descriptor->oneof_decl(idx); + if (fieldDescriptor->name().StartsWith("_")) { + continue; + } properties[fieldDescriptor->name()]["type"] = "oneOf"; } for (int idx = 0; idx < fields; ++idx) { diff --git a/ydb/core/viewer/yaml/yaml.h b/ydb/core/viewer/yaml/yaml.h index 8e0869c46ed9..8415cce3c57e 100644 --- a/ydb/core/viewer/yaml/yaml.h +++ b/ydb/core/viewer/yaml/yaml.h @@ -1,9 +1,9 @@ #pragma once +#include +#include #include #include -#include #include -#include struct TEnumSettings { bool ConvertToLowerCase = false; diff --git a/ydb/core/wrappers/s3_storage.h b/ydb/core/wrappers/s3_storage.h index d678f237616e..5af392c4f63c 100644 --- a/ydb/core/wrappers/s3_storage.h +++ b/ydb/core/wrappers/s3_storage.h @@ -3,7 +3,6 @@ #ifndef KIKIMR_DISABLE_S3_OPS #include "abstract.h" -#include "s3_storage_config.h" #include #include @@ -32,7 +31,7 @@ namespace NKikimr::NWrappers::NExternalStorage { -class TS3ExternalStorage: public IExternalStorageOperator, TS3User { +class TS3ExternalStorage: public IExternalStorageOperator { private: THolder Client; const Aws::Client::ClientConfiguration Config; diff --git a/ydb/core/wrappers/s3_storage_config.cpp b/ydb/core/wrappers/s3_storage_config.cpp index f0bc70e15fd0..9785eb7ed5ef 100644 --- a/ydb/core/wrappers/s3_storage_config.cpp +++ b/ydb/core/wrappers/s3_storage_config.cpp @@ -1,81 +1,15 @@ #include "s3_storage.h" #include "s3_storage_config.h" -#include -#include -#include -#include #include -#include -#include -#include #include #ifndef KIKIMR_DISABLE_S3_OPS namespace NKikimr::NWrappers::NExternalStorage { -using namespace Aws; -using namespace Aws::Auth; -using namespace Aws::Client; -using namespace Aws::S3; -using namespace Aws::S3::Model; -using namespace Aws::Utils::Stream; - namespace { -struct TCurlInitializer { - TCurlInitializer() { - curl_global_init(CURL_GLOBAL_ALL); - } - - ~TCurlInitializer() { - curl_global_cleanup(); - } -}; - -struct TApiInitializer { - TApiInitializer() { - Options.httpOptions.initAndCleanupCurl = false; - InitAPI(Options); - - Internal::CleanupEC2MetadataClient(); // speeds up config construction - } - - ~TApiInitializer() { - ShutdownAPI(Options); - } - -private: - SDKOptions Options; -}; - -class TApiOwner { -public: - void Ref() { - auto guard = Guard(Mutex); - if (!RefCount++) { - if (!CurlInitializer) { - CurlInitializer.Reset(new TCurlInitializer); - } - ApiInitializer.Reset(new TApiInitializer); - } - } - - void UnRef() { - auto guard = Guard(Mutex); - if (!--RefCount) { - ApiInitializer.Destroy(); - } - } - -private: - ui64 RefCount = 0; - TMutex Mutex; - THolder CurlInitializer; - THolder ApiInitializer; -}; - namespace NPrivate { template @@ -93,10 +27,10 @@ Aws::Client::ClientConfiguration ConfigFromSettings(const TSettings& settings) { switch (settings.scheme()) { case TSettings::HTTP: - config.scheme = Http::Scheme::HTTP; + config.scheme = Aws::Http::Scheme::HTTP; break; case TSettings::HTTPS: - config.scheme = Http::Scheme::HTTPS; + config.scheme = Aws::Http::Scheme::HTTPS; break; default: Y_ABORT("Unknown scheme"); @@ -114,22 +48,6 @@ Aws::Auth::AWSCredentials CredentialsFromSettings(const TSettings& settings) { } // anonymous -TS3User::TS3User(const TS3User& /*baseObject*/) { - Singleton()->Ref(); -} - -TS3User::TS3User(TS3User& /*baseObject*/) { - Singleton()->Ref(); -} - -TS3User::TS3User() { - Singleton()->Ref(); -} - -TS3User::~TS3User() { - Singleton()->UnRef(); -} - class TS3ThreadsPoolByEndpoint { private: diff --git a/ydb/core/wrappers/s3_storage_config.h b/ydb/core/wrappers/s3_storage_config.h index 544ccc74af82..b276bd79b363 100644 --- a/ydb/core/wrappers/s3_storage_config.h +++ b/ydb/core/wrappers/s3_storage_config.h @@ -17,14 +17,7 @@ namespace NKikimr::NWrappers::NExternalStorage { -struct TS3User { - TS3User(); - TS3User(const TS3User& baseObject); - TS3User(TS3User& baseObject); - ~TS3User(); -}; - -class TS3ExternalStorageConfig: public IExternalStorageConfig, TS3User { +class TS3ExternalStorageConfig: public IExternalStorageConfig { private: YDB_READONLY_DEF(TString, Bucket); Aws::Client::ClientConfiguration Config; diff --git a/ydb/core/wrappers/s3_wrapper_ut.cpp b/ydb/core/wrappers/s3_wrapper_ut.cpp index 4e310add8777..b8b0e185cde4 100644 --- a/ydb/core/wrappers/s3_wrapper_ut.cpp +++ b/ydb/core/wrappers/s3_wrapper_ut.cpp @@ -8,16 +8,33 @@ #include #include +#include #include #include +#include + using namespace NActors; using namespace NKikimr; using namespace NKikimr::NWrappers; using namespace Aws::S3::Model; -class TS3MockTest: public NUnitTest::TTestBase, private NExternalStorage::TS3User { +namespace { + +Aws::SDKOptions Options; + +Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); +} + +Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); +} + +} + +class TS3MockTest: public NUnitTest::TTestBase { using TS3Mock = NWrappers::NTestHelpers::TS3Mock; static auto MakeClientConfig(ui16 port) { @@ -48,8 +65,8 @@ class TS3MockTest: public NUnitTest::TTestBase, private NExternalStorage::TS3Use } void TearDown() override { - S3Mock.Reset(); Runtime.Reset(); + S3Mock.Reset(); } ui16 GetPort() const { diff --git a/ydb/core/wrappers/ut/ya.make b/ydb/core/wrappers/ut/ya.make index 61e02602c881..212e65cfc54f 100644 --- a/ydb/core/wrappers/ut/ya.make +++ b/ydb/core/wrappers/ut/ya.make @@ -9,6 +9,7 @@ IF (NOT OS_WINDOWS) ydb/library/actors/core library/cpp/digest/md5 library/cpp/testing/unittest + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core ydb/core/protos ydb/core/testlib/basics/default ydb/library/yql/minikql/comp_nodes/llvm14 diff --git a/ydb/core/ydb_convert/table_description.cpp b/ydb/core/ydb_convert/table_description.cpp index 2744041ea50d..f8b5b51c3321 100644 --- a/ydb/core/ydb_convert/table_description.cpp +++ b/ydb/core/ydb_convert/table_description.cpp @@ -839,8 +839,8 @@ void FillGlobalIndexSettings(Ydb::Table::GlobalIndexSettings& settings, NKikimrMiniKQL::TType splitKeyType; Ydb::Table::DescribeTableResult unused; FillColumnDescription(unused, splitKeyType, indexImplTableDescription); - FillTableBoundaryImpl( - *settings.mutable_partition_at_keys(), + FillTableBoundaryImpl( + settings, indexImplTableDescription, splitKeyType ); diff --git a/ydb/library/actors/wilson/wilson_trace.h b/ydb/library/actors/wilson/wilson_trace.h index 6d63ee87347d..65dade260da4 100644 --- a/ydb/library/actors/wilson/wilson_trace.h +++ b/ydb/library/actors/wilson/wilson_trace.h @@ -26,15 +26,19 @@ namespace NWilson { ui32 Raw; }; + public: + static constexpr ui8 MAX_VERBOSITY = 15; + static constexpr ui32 MAX_TIME_TO_LIVE = 4095; + private: TTraceId(TTrace traceId, ui64 spanId, ui8 verbosity, ui32 timeToLive) : TraceId(traceId) { if (timeToLive == Max()) { - timeToLive = 4095; + timeToLive = MAX_TIME_TO_LIVE; } - Y_ABORT_UNLESS(verbosity <= 15); - Y_ABORT_UNLESS(timeToLive <= 4095); + Y_ABORT_UNLESS(verbosity <= MAX_VERBOSITY); + Y_ABORT_UNLESS(timeToLive <= MAX_TIME_TO_LIVE); SpanId = spanId; Verbosity = verbosity; TimeToLive = timeToLive; diff --git a/ydb/library/yql/sql/sql.cpp b/ydb/library/yql/sql/sql.cpp index c981cd690a7e..2c8702d38035 100644 --- a/ydb/library/yql/sql/sql.cpp +++ b/ydb/library/yql/sql/sql.cpp @@ -15,18 +15,22 @@ namespace NSQLTranslation { NYql::TAstParseResult SqlToYql(const TString& query, const TTranslationSettings& settings, - NYql::TWarningRules* warningRules, NYql::TStmtParseInfo* stmtParseInfo) + NYql::TWarningRules* warningRules, NYql::TStmtParseInfo* stmtParseInfo, TTranslationSettings* effectiveSettings) { NYql::TAstParseResult result; TTranslationSettings parsedSettings(settings); - google::protobuf::Arena arena; - if (!parsedSettings.Arena) { - parsedSettings.Arena = &arena; - } if (!ParseTranslationSettings(query, parsedSettings, result.Issues)) { return result; } + if (effectiveSettings) { + *effectiveSettings = parsedSettings; + } + + google::protobuf::Arena arena; + if (!parsedSettings.Arena) { + parsedSettings.Arena = &arena; + } if (!parsedSettings.DeclaredNamedExprs.empty() && !parsedSettings.PgParser && parsedSettings.SyntaxVersion != 1) { result.Issues.AddIssue(NYql::YqlIssue(NYql::TPosition(), NYql::TIssuesIds::DEFAULT_ERROR, diff --git a/ydb/library/yql/sql/sql.h b/ydb/library/yql/sql/sql.h index f00252a5faec..eecdde9bae36 100644 --- a/ydb/library/yql/sql/sql.h +++ b/ydb/library/yql/sql/sql.h @@ -16,7 +16,8 @@ namespace NSQLTranslation { NYql::TAstParseResult SqlToYql(const TString& query, const TTranslationSettings& settings, - NYql::TWarningRules* warningRules = nullptr, NYql::TStmtParseInfo* stmtParseInfo = nullptr); + NYql::TWarningRules* warningRules = nullptr, NYql::TStmtParseInfo* stmtParseInfo = nullptr, + TTranslationSettings* effectiveSettings = nullptr); google::protobuf::Message* SqlAST(const TString& query, const TString& queryName, NYql::TIssues& issues, size_t maxErrors, const TTranslationSettings& settings = {}, ui16* actualSyntaxVersion = nullptr); ILexer::TPtr SqlLexer(const TString& query, NYql::TIssues& issues, const TTranslationSettings& settings = {}, ui16* actualSyntaxVersion = nullptr); diff --git a/ydb/library/yql/sql/v1/SQLv1.g.in b/ydb/library/yql/sql/v1/SQLv1.g.in index 03a414f8a109..76b691b81e56 100644 --- a/ydb/library/yql/sql/v1/SQLv1.g.in +++ b/ydb/library/yql/sql/v1/SQLv1.g.in @@ -597,7 +597,7 @@ alter_external_data_source_action: drop_external_data_source_stmt: DROP EXTERNAL DATA SOURCE (IF EXISTS)? object_ref; create_view_stmt: CREATE VIEW object_ref - with_table_settings + create_object_features? AS select_stmt ; @@ -625,7 +625,7 @@ drop_object_stmt: DROP OBJECT (IF EXISTS)? object_ref ; drop_object_features: WITH object_features; -object_feature_value: id_or_type | bind_parameter | STRING_VALUE; +object_feature_value: id_or_type | bind_parameter | STRING_VALUE | bool_value; object_feature_kv: an_id_or_type EQUALS object_feature_value; object_feature_flag: an_id_or_type; object_feature: object_feature_kv | object_feature_flag; diff --git a/ydb/library/yql/sql/v1/sql_query.cpp b/ydb/library/yql/sql/v1/sql_query.cpp index f6beb9992c07..f2c0a9e26e04 100644 --- a/ydb/library/yql/sql/v1/sql_query.cpp +++ b/ydb/library/yql/sql/v1/sql_query.cpp @@ -1233,8 +1233,14 @@ bool TSqlQuery::Statement(TVector& blocks, const TRule_sql_stmt_core& } std::map features; - ParseViewOptions(features, node.GetRule_with_table_settings4()); - ParseViewQuery(features, node.GetRule_select_stmt6()); + if (node.HasBlock4()) { + if (!ParseObjectFeatures(features, node.GetBlock4().GetRule_create_object_features1().GetRule_object_features2())) { + return false; + } + } + if (!ParseViewQuery(features, node.GetRule_select_stmt6())) { + return false; + } const TString objectId = Id(node.GetRule_object_ref3().GetRule_id_or_at2(), *this).second; constexpr const char* TypeId = "VIEW"; diff --git a/ydb/library/yql/sql/v1/sql_translation.cpp b/ydb/library/yql/sql/v1/sql_translation.cpp index e4c981af6d03..5b91a10cc88e 100644 --- a/ydb/library/yql/sql/v1/sql_translation.cpp +++ b/ydb/library/yql/sql/v1/sql_translation.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -53,21 +54,38 @@ TString CollectTokens(const TRule_select_stmt& selectStatement) { return tokenCollector.Tokens; } -NSQLTranslation::TTranslationSettings CreateViewTranslationSettings(const NSQLTranslation::TTranslationSettings& base) { - NSQLTranslation::TTranslationSettings settings; +bool RestoreContext( + TContext& ctx, const NSQLTranslation::TTranslationSettings& settings, const TString& contextRestorationQuery +) { + const TString queryName = "context restoration query"; + + const auto* ast = NSQLTranslationV1::SqlAST( + contextRestorationQuery, queryName, ctx.Issues, + settings.MaxErrors, settings.AnsiLexer, settings.Arena + ); + if (!ast) { + return false; + } - settings.ClusterMapping = base.ClusterMapping; - settings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW; + TSqlQuery query(ctx, ctx.Settings.Mode, true); + auto node = query.Build(static_cast(*ast)); - return settings; + return node && node->Init(ctx, nullptr) && node->Translate(ctx); } -TNodePtr BuildViewSelect(const TRule_select_stmt& query, TContext& ctx) { - const auto viewTranslationSettings = CreateViewTranslationSettings(ctx.Settings); - TContext viewParsingContext(viewTranslationSettings, {}, ctx.Issues); - TSqlSelect select(viewParsingContext, viewTranslationSettings.Mode); +TNodePtr BuildViewSelect( + const TRule_select_stmt& selectQuery, + TContext& parentContext, + const TString& contextRestorationQuery +) { + TContext context(parentContext.Settings, {}, parentContext.Issues); + RestoreContext(context, context.Settings, contextRestorationQuery); + + context.Settings.Mode = NSQLTranslation::ESqlMode::LIMITED_VIEW; + + TSqlSelect select(context, context.Settings.Mode); TPosition pos; - auto source = select.Build(query, pos); + auto source = select.Build(selectQuery, pos); if (!source) { return nullptr; } @@ -76,7 +94,7 @@ TNodePtr BuildViewSelect(const TRule_select_stmt& query, TContext& ctx) { std::move(source), false, false, - viewParsingContext.Scoped + context.Scoped ); } @@ -1629,16 +1647,6 @@ namespace { return true; } - bool StoreBool(const TRule_table_setting_value& from, TDeferredAtom& to, TContext& ctx) { - if (!from.HasAlt_table_setting_value6()) { - return false; - } - // bool_value - const TString value = to_lower(ctx.Token(from.GetAlt_table_setting_value6().GetRule_bool_value1().GetToken1())); - to = TDeferredAtom(BuildLiteralBool(ctx.Pos(), FromString(value)), ctx); - return true; - } - bool StoreSplitBoundary(const TRule_literal_value_list& boundary, TVector>& to, TSqlExpression& expr, TContext& ctx) { TVector boundaryKeys; @@ -1765,26 +1773,6 @@ namespace { return true; } - bool StoreViewOptionsEntry(const TIdentifier& id, - const TRule_table_setting_value& value, - std::map& features, - TContext& ctx) { - const auto name = to_lower(id.Name); - const auto publicName = to_upper(name); - - if (features.find(name) != features.end()) { - ctx.Error(ctx.Pos()) << publicName << " is a duplicate"; - return false; - } - - if (!StoreBool(value, features[name], ctx)) { - ctx.Error(ctx.Pos()) << "Value of " << publicName << " must be a bool"; - return false; - } - - return true; - } - template struct TPatternComponent { TBasicString Prefix; @@ -4376,7 +4364,7 @@ bool TSqlTranslation::BindParameterClause(const TRule_bind_parameter& node, TDef } bool TSqlTranslation::ObjectFeatureValueClause(const TRule_object_feature_value& node, TDeferredAtom& result) { - // object_feature_value: an_id_or_type | bind_parameter; + // object_feature_value: id_or_type | bind_parameter | STRING_VALUE | bool_value; switch (node.Alt_case()) { case TRule_object_feature_value::kAltObjectFeatureValue1: { @@ -4401,6 +4389,12 @@ bool TSqlTranslation::ObjectFeatureValueClause(const TRule_object_feature_value& result = TDeferredAtom(Ctx.Pos(), strValue->Content); break; } + case TRule_object_feature_value::kAltObjectFeatureValue4: + { + TString value = Ctx.Token(node.GetAlt_object_feature_value4().GetRule_bool_value1().GetToken1()); + result = TDeferredAtom(BuildLiteralBool(Ctx.Pos(), FromString(value)), Ctx); + break; + } case TRule_object_feature_value::ALT_NOT_SET: Y_ABORT("You should change implementation according to grammar changes"); } @@ -4590,38 +4584,32 @@ bool TSqlTranslation::ValidateExternalTable(const TCreateTableParameters& params return true; } -bool TSqlTranslation::ParseViewOptions(std::map& features, - const TRule_with_table_settings& options) { - const auto& firstEntry = options.GetRule_table_settings_entry3(); - if (!StoreViewOptionsEntry(IdEx(firstEntry.GetRule_an_id1(), *this), - firstEntry.GetRule_table_setting_value3(), - features, - Ctx)) { - return false; - } - for (const auto& block : options.GetBlock4()) { - const auto& entry = block.GetRule_table_settings_entry2(); - if (!StoreViewOptionsEntry(IdEx(entry.GetRule_an_id1(), *this), - entry.GetRule_table_setting_value3(), - features, - Ctx)) { - return false; +bool TSqlTranslation::ParseViewQuery( + std::map& features, + const TRule_select_stmt& query +) { + TString queryText = CollectTokens(query); + TString contextRestorationQuery; + { + const auto& service = Ctx.Scoped->CurrService; + const auto& cluster = Ctx.Scoped->CurrCluster; + const auto effectivePathPrefix = Ctx.GetPrefixPath(service, cluster); + + // TO DO: capture all runtime pragmas in a similar fashion. + if (effectivePathPrefix != Ctx.Settings.PathPrefix) { + contextRestorationQuery = TStringBuilder() << "PRAGMA TablePathPrefix = \"" << effectivePathPrefix << "\";\n"; } - } - if (const auto securityInvoker = features.find("security_invoker"); - securityInvoker == features.end() || securityInvoker->second.Build()->GetLiteralValue() != "true") { - Ctx.Error(Ctx.Pos()) << "SECURITY_INVOKER option must be explicitly enabled"; - return false; - } - return true; -} -bool TSqlTranslation::ParseViewQuery(std::map& features, - const TRule_select_stmt& query) { - const TString queryText = CollectTokens(query); - features["query_text"] = {Ctx.Pos(), queryText}; + // TO DO: capture other compilation-affecting statements except USE. + if (cluster.GetLiteral() && *cluster.GetLiteral() != Ctx.Settings.DefaultCluster) { + contextRestorationQuery = TStringBuilder() << "USE " << *cluster.GetLiteral() << ";\n"; + } + } + features["query_text"] = { Ctx.Pos(), contextRestorationQuery + queryText }; - const auto viewSelect = BuildViewSelect(query, Ctx); + // AST is needed for ready-made validation of CREATE VIEW statement. + // Query is stored as plain text, not AST. + const auto viewSelect = BuildViewSelect(query, Ctx, contextRestorationQuery); if (!viewSelect) { return false; } diff --git a/ydb/library/yql/sql/v1/sql_ut.cpp b/ydb/library/yql/sql/v1/sql_ut.cpp index 540453e07a93..5fda9c17726d 100644 --- a/ydb/library/yql/sql/v1/sql_ut.cpp +++ b/ydb/library/yql/sql/v1/sql_ut.cpp @@ -6592,24 +6592,6 @@ Y_UNIT_TEST_SUITE(TViewSyntaxTest) { UNIT_ASSERT_C(res.Root, res.Issues.ToString()); } - Y_UNIT_TEST(CreateViewNoSecurityInvoker) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE VIEW TheView AS SELECT 1; - )" - ); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Unexpected token 'AS' : syntax error"); - } - - Y_UNIT_TEST(CreateViewSecurityInvokerTurnedOff) { - NYql::TAstParseResult res = SqlToYql(R"( - USE plato; - CREATE VIEW TheView WITH (security_invoker = FALSE) AS SELECT 1; - )" - ); - UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "SECURITY_INVOKER option must be explicitly enabled"); - } - Y_UNIT_TEST(CreateViewFromTable) { constexpr const char* path = "/PathPrefix/TheView"; constexpr const char* query = R"( diff --git a/ydb/public/api/protos/draft/ydb_maintenance.proto b/ydb/public/api/protos/draft/ydb_maintenance.proto index d749ab6709a0..371f35db07ca 100644 --- a/ydb/public/api/protos/draft/ydb_maintenance.proto +++ b/ydb/public/api/protos/draft/ydb_maintenance.proto @@ -161,12 +161,15 @@ message ActionState { ACTION_REASON_WRONG_REQUEST = 6; // Too many unavailable nodes with system tablets. ACTION_REASON_SYS_TABLETS_NODE_LIMIT_REACHED = 7; + // Generic reason. + ACTION_REASON_GENERIC = 8; } Action action = 1; ActionUid action_uid = 2; ActionStatus status = 3; ActionReason reason = 4; + string reason_details = 6; google.protobuf.Timestamp deadline = 5; } diff --git a/ydb/public/api/protos/draft/ymq.proto b/ydb/public/api/protos/draft/ymq.proto index 22e1a3f9d7e7..b699c597ce81 100644 --- a/ydb/public/api/protos/draft/ymq.proto +++ b/ydb/public/api/protos/draft/ymq.proto @@ -194,8 +194,8 @@ message ReceiveMessageResponse { message Message { map attributes = 1; string body = 2; - string m_d_5_of_body = 3; - string m_d_5_of_message_attributes = 4; + string m_d_5_of_body = 3 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; + string m_d_5_of_message_attributes = 4 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; map message_attributes = 5; string message_id = 6; string receipt_handle = 7; @@ -221,9 +221,9 @@ message SendMessageResponse { } message SendMessageResult { - string m_d_5_of_message_attributes = 1; - string m_d_5_of_message_body= 2; - string m_d_5_of_message_system_attributes= 3; + string m_d_5_of_message_attributes = 1 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; + string m_d_5_of_message_body= 2 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; + string m_d_5_of_message_system_attributes= 3 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; string message_id = 4; string sequence_number = 5; } @@ -248,10 +248,10 @@ message SendMessageBatchRequestEntry { message SendMessageBatchResultEntry { string id = 1; - string m_d_5_of_message_body = 2; + string m_d_5_of_message_body = 2 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; string message_id = 3; - string m_d_5_of_message_attributes = 4; - string m_d_5_of_message_system_attributes = 5; + string m_d_5_of_message_attributes = 4 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; + string m_d_5_of_message_system_attributes = 5 [(Ydb.FieldTransformation.FieldTransformer) = TRANSFORM_EMPTY_TO_NOTHING]; string sequence_number = 6; } diff --git a/ydb/public/api/protos/out/out.cpp b/ydb/public/api/protos/out/out.cpp index c8c5c7abfaff..124e496898a0 100644 --- a/ydb/public/api/protos/out/out.cpp +++ b/ydb/public/api/protos/out/out.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -29,3 +30,11 @@ Y_DECLARE_OUT_SPEC(, Ydb::Export::ExportProgress::Progress, stream, value) { Y_DECLARE_OUT_SPEC(, Ydb::Import::ImportProgress::Progress, stream, value) { stream << Ydb::Import::ImportProgress_Progress_Name(value); } + +Y_DECLARE_OUT_SPEC(, Ydb::Maintenance::ActionState::ActionStatus, stream, value) { + stream << Ydb::Maintenance::ActionState::ActionStatus_Name(value); +} + +Y_DECLARE_OUT_SPEC(, Ydb::Maintenance::ActionState::ActionReason, stream, value) { + stream << Ydb::Maintenance::ActionState::ActionReason_Name(value); +} diff --git a/ydb/public/sdk/cpp/client/ydb_table/table.cpp b/ydb/public/sdk/cpp/client/ydb_table/table.cpp index 656f0b087e00..917a11bbb1d4 100644 --- a/ydb/public/sdk/cpp/client/ydb_table/table.cpp +++ b/ydb/public/sdk/cpp/client/ydb_table/table.cpp @@ -485,6 +485,10 @@ class TTableDescription::TImpl { Indexes_.emplace_back(TIndexDescription(indexName, type, indexColumns, dataColumns)); } + void AddSecondaryIndex(const TIndexDescription& indexDescription) { + Indexes_.emplace_back(indexDescription); + } + void AddChangefeed(const TString& name, EChangefeedMode mode, EChangefeedFormat format) { Changefeeds_.emplace_back(name, mode, format); } @@ -757,6 +761,10 @@ void TTableDescription::AddSecondaryIndex(const TString& indexName, EIndexType t Impl_->AddSecondaryIndex(indexName, type, indexColumns, dataColumns); } +void TTableDescription::AddSecondaryIndex(const TIndexDescription& indexDescription) { + Impl_->AddSecondaryIndex(indexDescription); +} + void TTableDescription::AddSyncSecondaryIndex(const TString& indexName, const TVector& indexColumns) { AddSecondaryIndex(indexName, EIndexType::GlobalSync, indexColumns); } @@ -1191,6 +1199,11 @@ TTableBuilder& TTableBuilder::SetPrimaryKeyColumn(const TString& primaryKeyColum return *this; } +TTableBuilder& TTableBuilder::AddSecondaryIndex(const TIndexDescription& indexDescription) { + TableDescription_.AddSecondaryIndex(indexDescription); + return *this; +} + TTableBuilder& TTableBuilder::AddSecondaryIndex(const TString& indexName, EIndexType type, const TVector& indexColumns, const TVector& dataColumns) { TableDescription_.AddSecondaryIndex(indexName, type, indexColumns, dataColumns); return *this; diff --git a/ydb/public/sdk/cpp/client/ydb_table/table.h b/ydb/public/sdk/cpp/client/ydb_table/table.h index ba54abb380d8..1827c930457e 100644 --- a/ydb/public/sdk/cpp/client/ydb_table/table.h +++ b/ydb/public/sdk/cpp/client/ydb_table/table.h @@ -183,10 +183,10 @@ struct TExplicitPartitions { using TSelf = TExplicitPartitions; FLUENT_SETTING_VECTOR(TValue, SplitPoints); - + template static TExplicitPartitions FromProto(const TProto& proto); - + void SerializeTo(Ydb::Table::ExplicitPartitions& proto) const; }; @@ -609,6 +609,7 @@ class TTableDescription { // common void AddSecondaryIndex(const TString& indexName, EIndexType type, const TVector& indexColumns); void AddSecondaryIndex(const TString& indexName, EIndexType type, const TVector& indexColumns, const TVector& dataColumns); + void AddSecondaryIndex(const TIndexDescription& indexDescription); // sync void AddSyncSecondaryIndex(const TString& indexName, const TVector& indexColumns); void AddSyncSecondaryIndex(const TString& indexName, const TVector& indexColumns, const TVector& dataColumns); @@ -820,6 +821,7 @@ class TTableBuilder { TTableBuilder& AddSerialColumn(const TString& name, const EPrimitiveType& type, TSequenceDescription sequenceDescription, const TString& family = TString()); // common + TTableBuilder& AddSecondaryIndex(const TIndexDescription& indexDescription); TTableBuilder& AddSecondaryIndex(const TString& indexName, EIndexType type, const TVector& indexColumns, const TVector& dataColumns); TTableBuilder& AddSecondaryIndex(const TString& indexName, EIndexType type, const TVector& indexColumns); TTableBuilder& AddSecondaryIndex(const TString& indexName, EIndexType type, const TString& indexColumn); diff --git a/ydb/services/metadata/abstract/parsing.h b/ydb/services/metadata/abstract/parsing.h index 6dfe093be319..73c9f7507068 100644 --- a/ydb/services/metadata/abstract/parsing.h +++ b/ydb/services/metadata/abstract/parsing.h @@ -74,8 +74,10 @@ class TObjectSettingsImpl { NNodes::TCoNameValueTuple tuple = maybeTuple.Cast(); if (auto maybeAtom = tuple.Value().template Maybe()) { Features.emplace(tuple.Name().Value(), maybeAtom.Cast().Value()); - } else if (auto maybeDataCtor = tuple.Value().template Maybe()) { - Features.emplace(tuple.Name().Value(), maybeDataCtor.Cast().Literal().Cast().Value()); + } else if (auto maybeInt = tuple.Value().template Maybe()) { + Features.emplace(tuple.Name().Value(), maybeInt.Cast().Literal().Cast().Value()); + } else if (auto maybeBool = tuple.Value().template Maybe()) { + Features.emplace(tuple.Name().Value(), maybeBool.Cast().Literal().Cast().Value()); } } } diff --git a/ydb/services/metadata/manager/abstract.h b/ydb/services/metadata/manager/abstract.h index 41763dcc8300..74a34997a1c9 100644 --- a/ydb/services/metadata/manager/abstract.h +++ b/ydb/services/metadata/manager/abstract.h @@ -14,6 +14,7 @@ #include #include +#include namespace NKikimr::NMetadata::NModifications { @@ -77,6 +78,7 @@ class IOperationsManager { YDB_ACCESSOR_DEF(TString, Database); using TActorSystemPtr = TActorSystem*; YDB_ACCESSOR_DEF(TActorSystemPtr, ActorSystem); + YDB_ACCESSOR_DEF(NSQLTranslation::TTranslationSettings, TranslationSettings); }; class TInternalModificationContext { diff --git a/ydb/services/metadata/manager/ya.make b/ydb/services/metadata/manager/ya.make index 79beeb811c81..1fd0b7871af9 100644 --- a/ydb/services/metadata/manager/ya.make +++ b/ydb/services/metadata/manager/ya.make @@ -21,6 +21,7 @@ PEERDIR( ydb/library/accessor ydb/library/actors/core ydb/library/table_creator + ydb/library/yql/sql/settings ydb/public/api/protos ydb/core/protos ydb/services/bg_tasks/abstract diff --git a/ydb/services/ydb/backup_ut/ya.make b/ydb/services/ydb/backup_ut/ya.make new file mode 100644 index 000000000000..39732a69bd9c --- /dev/null +++ b/ydb/services/ydb/backup_ut/ya.make @@ -0,0 +1,32 @@ +UNITTEST_FOR(ydb/services/ydb) + +FORK_SUBTESTS() + +IF (SANITIZER_TYPE == "thread" OR WITH_VALGRIND) + SIZE(LARGE) + TAG(ya:fat) +ELSE() + SIZE(MEDIUM) +ENDIF() + +SRCS( + ydb_backup_ut.cpp +) + +PEERDIR( + ydb/core/testlib/default + ydb/core/wrappers/ut_helpers + ydb/public/lib/ydb_cli/dump + ydb/public/sdk/cpp/client/ydb_export + ydb/public/sdk/cpp/client/ydb_import + ydb/public/sdk/cpp/client/ydb_operation + ydb/public/sdk/cpp/client/ydb_result + ydb/public/sdk/cpp/client/ydb_table + ydb/public/sdk/cpp/client/ydb_value + ydb/library/backup + contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp b/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp new file mode 100644 index 000000000000..f7e9371cf9c3 --- /dev/null +++ b/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp @@ -0,0 +1,626 @@ +#include "ydb_common_ut.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +using namespace NYdb; +using namespace NYdb::NTable; + +namespace NYdb::NTable { + +bool operator==(const TValue& lhs, const TValue& rhs) { + return google::protobuf::util::MessageDifferencer::Equals(lhs.GetProto(), rhs.GetProto()); +} + +bool operator==(const TKeyBound& lhs, const TKeyBound& rhs) { + return lhs.GetValue() == rhs.GetValue() && lhs.IsInclusive() == rhs.IsInclusive(); +} + +bool operator==(const TKeyRange& lhs, const TKeyRange& rhs) { + return lhs.From() == lhs.From() && lhs.To() == rhs.To(); +} + +} + +namespace { + +#define DEBUG_HINT (TStringBuilder() << "at line " << __LINE__) + +void ExecuteDataDefinitionQuery(TSession& session, const TString& script) { + const auto result = session.ExecuteSchemeQuery(script).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), "script:\n" << script << "\nissues:\n" << result.GetIssues().ToString()); +} + +TDataQueryResult ExecuteDataModificationQuery(TSession& session, + const TString& script, + const TExecDataQuerySettings& settings = {} +) { + const auto result = session.ExecuteDataQuery( + script, + TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(), + settings + ).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), "script:\n" << script << "\nissues:\n" << result.GetIssues().ToString()); + + return result; +} + +TDataQueryResult GetTableContent(TSession& session, const char* table) { + return ExecuteDataModificationQuery(session, Sprintf(R"( + SELECT * FROM `%s` ORDER BY Key; + )", table + )); +} + +void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) { + const auto& firstResults = first.GetResultSets(); + const auto& secondResults = second.GetResultSets(); + + UNIT_ASSERT_VALUES_EQUAL(firstResults.size(), secondResults.size()); + for (size_t i = 0; i < firstResults.size(); ++i) { + UNIT_ASSERT_STRINGS_EQUAL( + FormatResultSetYson(firstResults[i]), + FormatResultSetYson(secondResults[i]) + ); + } +} + +TTableDescription GetTableDescription(TSession& session, const TString& path, + const TDescribeTableSettings& settings = {} +) { + auto describeResult = session.DescribeTable(path, settings).ExtractValueSync(); + UNIT_ASSERT_C(describeResult.IsSuccess(), describeResult.GetIssues().ToString()); + return describeResult.GetTableDescription(); +} + +auto CreateMinPartitionsChecker(ui32 expectedMinPartitions, const TString& debugHint = "") { + return [=](const TTableDescription& tableDescription) { + UNIT_ASSERT_VALUES_EQUAL_C( + tableDescription.GetPartitioningSettings().GetMinPartitionsCount(), + expectedMinPartitions, + debugHint + ); + }; +} + +void CheckTableDescription(TSession& session, const TString& path, auto&& checker, + const TDescribeTableSettings& settings = {} +) { + checker(GetTableDescription(session, path, settings)); +} + +using TBackupFunction = std::function; +using TRestoreFunction = std::function; + +void TestTableContentIsPreserved( + const char* table, TSession& session, TBackupFunction&& backup, TRestoreFunction&& restore +) { + ExecuteDataDefinitionQuery(session, Sprintf(R"( + CREATE TABLE `%s` ( + Key Uint32, + Value Utf8, + PRIMARY KEY (Key) + ); + )", + table + )); + ExecuteDataModificationQuery(session, Sprintf(R"( + UPSERT INTO `%s` ( + Key, + Value + ) + VALUES + (1, "one"), + (2, "two"), + (3, "three"), + (4, "four"), + (5, "five"); + )", + table + )); + const auto originalContent = GetTableContent(session, table); + + backup(table); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + DROP TABLE `%s`; + )", table + )); + + restore(table); + CompareResults(GetTableContent(session, table), originalContent); +} + +void TestTablePartitioningSettingsArePreserved( + const char* table, ui32 minPartitions, TSession& session, TBackupFunction&& backup, TRestoreFunction&& restore +) { + ExecuteDataDefinitionQuery(session, Sprintf(R"( + CREATE TABLE `%s` ( + Key Uint32, + Value Utf8, + PRIMARY KEY (Key) + ) + WITH ( + AUTO_PARTITIONING_BY_LOAD = ENABLED, + AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = %u + ); + )", + table, minPartitions + )); + CheckTableDescription(session, table, CreateMinPartitionsChecker(minPartitions, DEBUG_HINT)); + + backup(table); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + DROP TABLE `%s`; + )", table + )); + + restore(table); + CheckTableDescription(session, table, CreateMinPartitionsChecker(minPartitions, DEBUG_HINT)); +} + +void TestIndexTablePartitioningSettingsArePreserved( + const char* table, const char* index, ui32 minIndexPartitions, TSession& session, + TBackupFunction&& backup, TRestoreFunction&& restore +) { + const TString indexTablePath = JoinFsPaths(table, index, "indexImplTable"); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + CREATE TABLE `%s` ( + Key Uint32, + Value Uint32, + PRIMARY KEY (Key), + INDEX %s GLOBAL ON (Value) + ); + )", + table, index + )); + ExecuteDataDefinitionQuery(session, Sprintf(R"( + ALTER TABLE `%s` ALTER INDEX %s SET ( + AUTO_PARTITIONING_BY_LOAD = ENABLED, + AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = %u + ); + )", table, index, minIndexPartitions + )); + CheckTableDescription(session, indexTablePath, CreateMinPartitionsChecker(minIndexPartitions, DEBUG_HINT)); + + backup(table); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + DROP TABLE `%s`; + )", table + )); + + restore(table); + CheckTableDescription(session, indexTablePath, CreateMinPartitionsChecker(minIndexPartitions, DEBUG_HINT)); +} + +void TestTableSplitBoundariesArePreserved( + const char* table, ui64 partitions, TSession& session, TBackupFunction&& backup, TRestoreFunction&& restore +) { + ExecuteDataDefinitionQuery(session, Sprintf(R"( + CREATE TABLE `%s` ( + Key Uint32, + Value Utf8, + PRIMARY KEY (Key) + ) + WITH ( + PARTITION_AT_KEYS = (1, 2, 4, 8, 16, 32, 64, 128, 256) + ); + )", + table + )); + const auto describeSettings = TDescribeTableSettings() + .WithTableStatistics(true) + .WithKeyShardBoundary(true); + const auto originalTableDescription = GetTableDescription(session, table, describeSettings); + UNIT_ASSERT_VALUES_EQUAL(originalTableDescription.GetPartitionsCount(), partitions); + const auto& originalKeyRanges = originalTableDescription.GetKeyRanges(); + UNIT_ASSERT_VALUES_EQUAL(originalKeyRanges.size(), partitions); + + backup(table); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + DROP TABLE `%s`; + )", table + )); + + restore(table); + const auto restoredTableDescription = GetTableDescription(session, table, describeSettings); + UNIT_ASSERT_VALUES_EQUAL(restoredTableDescription.GetPartitionsCount(), partitions); + const auto& restoredKeyRanges = restoredTableDescription.GetKeyRanges(); + UNIT_ASSERT_VALUES_EQUAL(restoredKeyRanges.size(), partitions); + UNIT_ASSERT_EQUAL(restoredTableDescription.GetKeyRanges(), originalKeyRanges); +} + +void TestIndexTableSplitBoundariesArePreserved( + const char* table, const char* index, ui64 indexPartitions, TSession& session, + TBackupFunction&& backup, TRestoreFunction&& restore +) { + const TString indexTablePath = JoinFsPaths(table, index, "indexImplTable"); + + { + TExplicitPartitions indexPartitionBoundaries; + for (ui32 boundary : {1, 2, 4, 8, 16, 32, 64, 128, 256}) { + indexPartitionBoundaries.AppendSplitPoints( + // split boundary is technically always a tuple + TValueBuilder().BeginTuple().AddElement().OptionalUint32(boundary).EndTuple().Build() + ); + } + // By default indexImplTable has auto partitioning by size enabled, + // so you must set min partition count for partitions to not merge immediately after indexImplTable is built. + TPartitioningSettingsBuilder partitioningSettingsBuilder; + partitioningSettingsBuilder + .SetMinPartitionsCount(indexPartitions) + .SetMaxPartitionsCount(indexPartitions); + + const auto indexSettings = TGlobalIndexSettings{ + .PartitioningSettings = partitioningSettingsBuilder.Build(), + .Partitions = std::move(indexPartitionBoundaries) + }; + + auto tableBuilder = TTableBuilder() + .AddNullableColumn("Key", EPrimitiveType::Uint32) + .AddNullableColumn("Value", EPrimitiveType::Uint32) + .SetPrimaryKeyColumn("Key") + .AddSecondaryIndex(TIndexDescription("byValue", EIndexType::GlobalSync, { "Value" }, {}, { indexSettings })); + + const auto result = session.CreateTable(table, tableBuilder.Build()).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); + } + const auto describeSettings = TDescribeTableSettings() + .WithTableStatistics(true) + .WithKeyShardBoundary(true); + const auto originalIndexTableDescription = GetTableDescription( + session, indexTablePath, describeSettings + ); + UNIT_ASSERT_VALUES_EQUAL(originalIndexTableDescription.GetPartitionsCount(), indexPartitions); + const auto& originalKeyRanges = originalIndexTableDescription.GetKeyRanges(); + UNIT_ASSERT_VALUES_EQUAL(originalKeyRanges.size(), indexPartitions); + + backup(table); + + ExecuteDataDefinitionQuery(session, Sprintf(R"( + DROP TABLE `%s`; + )", table + )); + + restore(table); + const auto restoredIndexTableDescription = GetTableDescription( + session, indexTablePath, describeSettings + ); + UNIT_ASSERT_VALUES_EQUAL(restoredIndexTableDescription.GetPartitionsCount(), indexPartitions); + const auto& restoredKeyRanges = restoredIndexTableDescription.GetKeyRanges(); + UNIT_ASSERT_VALUES_EQUAL(restoredKeyRanges.size(), indexPartitions); + UNIT_ASSERT_EQUAL(restoredKeyRanges, originalKeyRanges); +} + +} + +Y_UNIT_TEST_SUITE(BackupRestore) { + + void Restore(NDump::TClient& client, const TFsPath& sourceFile, const TString& dbPath) { + auto result = client.Restore(sourceFile, dbPath); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); + } + + auto CreateBackupLambda(const TDriver& driver, const TFsPath& pathToBackup, bool schemaOnly = false) { + return [&driver, &pathToBackup, schemaOnly](const char* table) { + Y_UNUSED(table); + // TO DO: implement NDump::TClient::Dump and call it instead of BackupFolder + NBackup::BackupFolder(driver, "/Root", ".", pathToBackup, {}, schemaOnly, false); + }; + } + + auto CreateRestoreLambda(const TDriver& driver, const TFsPath& pathToBackup) { + return [&driver, &pathToBackup](const char* table) { + Y_UNUSED(table); + NDump::TClient backupClient(driver); + Restore(backupClient, pathToBackup, "/Root"); + }; + } + + Y_UNIT_TEST(RestoreTableContent) { + TKikimrWithGrpcAndRootSchema server; + auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))); + TTableClient tableClient(driver); + auto session = tableClient.GetSession().ExtractValueSync().GetSession(); + TTempDir tempDir; + const auto& pathToBackup = tempDir.Path(); + + constexpr const char* table = "/Root/table"; + + TestTableContentIsPreserved( + table, + session, + CreateBackupLambda(driver, pathToBackup), + CreateRestoreLambda(driver, pathToBackup) + ); + } + + Y_UNIT_TEST(RestoreTablePartitioningSettings) { + TKikimrWithGrpcAndRootSchema server; + auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))); + TTableClient tableClient(driver); + auto session = tableClient.GetSession().ExtractValueSync().GetSession(); + TTempDir tempDir; + const auto& pathToBackup = tempDir.Path(); + + constexpr const char* table = "/Root/table"; + constexpr ui32 minPartitions = 10; + + TestTablePartitioningSettingsArePreserved( + table, + minPartitions, + session, + CreateBackupLambda(driver, pathToBackup, true), + CreateRestoreLambda(driver, pathToBackup) + ); + } + + Y_UNIT_TEST(RestoreIndexTablePartitioningSettings) { + TKikimrWithGrpcAndRootSchema server; + auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))); + TTableClient tableClient(driver); + auto session = tableClient.GetSession().ExtractValueSync().GetSession(); + TTempDir tempDir; + const auto& pathToBackup = tempDir.Path(); + + constexpr const char* table = "/Root/table"; + constexpr const char* index = "byValue"; + constexpr ui32 minIndexPartitions = 10; + + TestIndexTablePartitioningSettingsArePreserved( + table, + index, + minIndexPartitions, + session, + CreateBackupLambda(driver, pathToBackup, true), + CreateRestoreLambda(driver, pathToBackup) + ); + } + + Y_UNIT_TEST(RestoreTableSplitBoundaries) { + TKikimrWithGrpcAndRootSchema server; + auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))); + TTableClient tableClient(driver); + auto session = tableClient.GetSession().ExtractValueSync().GetSession(); + TTempDir tempDir; + const auto& pathToBackup = tempDir.Path(); + + constexpr const char* table = "/Root/table"; + constexpr ui64 partitions = 10; + + TestTableSplitBoundariesArePreserved( + table, + partitions, + session, + CreateBackupLambda(driver, pathToBackup, true), + CreateRestoreLambda(driver, pathToBackup) + ); + } + + // TO DO: test index impl table split boundaries restoration from a backup +} + +Y_UNIT_TEST_SUITE(BackupRestoreS3) { + + Aws::SDKOptions Options; + + Y_TEST_HOOK_BEFORE_RUN(InitAwsAPI) { + Aws::InitAPI(Options); + } + + Y_TEST_HOOK_AFTER_RUN(ShutdownAwsAPI) { + Aws::ShutdownAPI(Options); + } + + using NKikimr::NWrappers::NTestHelpers::TS3Mock; + + class TS3TestEnv { + TKikimrWithGrpcAndRootSchema server; + TDriver driver; + TTableClient tableClient; + TSession session; + ui16 s3Port; + TS3Mock s3Mock; + // required for exports to function + TDataShardExportFactory dataShardExportFactory; + + public: + TS3TestEnv() + : driver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))) + , tableClient(driver) + , session(tableClient.CreateSession().ExtractValueSync().GetSession()) + , s3Port(server.GetPortManager().GetPort()) + , s3Mock({}, TS3Mock::TSettings(s3Port)) + { + UNIT_ASSERT_C(s3Mock.Start(), s3Mock.GetError()); + + auto& runtime = *server.GetRuntime(); + runtime.SetLogPriority(NKikimrServices::TX_PROXY, NLog::EPriority::PRI_DEBUG); + runtime.GetAppData().DataShardExportFactory = &dataShardExportFactory; + } + + TKikimrWithGrpcAndRootSchema& GetServer() { + return server; + } + + const TDriver& GetDriver() const { + return driver; + } + + TSession& GetSession() { + return session; + } + + ui16 GetS3Port() const { + return s3Port; + } + }; + + template + bool WaitForOperation(NOperation::TOperationClient& client, NOperationId::TOperationId id, + int retries = 10, TDuration sleepDuration = TDuration::MilliSeconds(100) + ) { + for (int retry = 0; retry <= retries; ++retry) { + auto result = client.Get(id).ExtractValueSync(); + if (result.Ready()) { + UNIT_ASSERT_VALUES_EQUAL_C( + result.Status().GetStatus(), EStatus::SUCCESS, + result.Status().GetIssues().ToString() + ); + return true; + } + Sleep(sleepDuration *= 2); + } + return false; + } + + void ExportToS3(NExport::TExportClient& exportClient, ui16 s3Port, NOperation::TOperationClient& operationClient, + const TString& source, const TString& destination + ) { + // The exact values for Bucket, AccessKey and SecretKey do not matter if the S3 backend is TS3Mock. + // Any non-empty strings should do. + const auto exportSettings = NExport::TExportToS3Settings() + .Endpoint(Sprintf("localhost:%u", s3Port)) + .Scheme(ES3Scheme::HTTP) + .Bucket("test_bucket") + .AccessKey("test_key") + .SecretKey("test_secret") + .AppendItem(NExport::TExportToS3Settings::TItem{.Src = source, .Dst = destination}); + + auto response = exportClient.ExportToS3(exportSettings).ExtractValueSync(); + UNIT_ASSERT_C(WaitForOperation(operationClient, response.Id()), + Sprintf("The export from %s to %s did not complete within the allocated time.", + source.c_str(), destination.c_str() + ) + ); + } + + void ImportFromS3(NImport::TImportClient& importClient, ui16 s3Port, NOperation::TOperationClient& operationClient, + const TString& source, const TString& destination + ) { + // The exact values for Bucket, AccessKey and SecretKey do not matter if the S3 backend is TS3Mock. + // Any non-empty strings should do. + const auto importSettings = NImport::TImportFromS3Settings() + .Endpoint(Sprintf("localhost:%u", s3Port)) + .Scheme(ES3Scheme::HTTP) + .Bucket("test_bucket") + .AccessKey("test_key") + .SecretKey("test_secret") + .AppendItem(NImport::TImportFromS3Settings::TItem{.Src = source, .Dst = destination}); + + auto response = importClient.ImportFromS3(importSettings).ExtractValueSync(); + UNIT_ASSERT_C(WaitForOperation(operationClient, response.Id()), + Sprintf("The import from %s to %s did not complete within the allocated time.", + source.c_str(), destination.c_str() + ) + ); + } + + auto CreateBackupLambda(const TDriver& driver, ui16 s3Port) { + return [&driver, s3Port](const char* table) { + NExport::TExportClient exportClient(driver); + NOperation::TOperationClient operationClient(driver); + ExportToS3(exportClient, s3Port, operationClient, table, "table"); + }; + } + + auto CreateRestoreLambda(const TDriver& driver, ui16 s3Port) { + return [&driver, s3Port](const char* table) { + NImport::TImportClient importClient(driver); + NOperation::TOperationClient operationClient(driver); + ImportFromS3(importClient, s3Port, operationClient, "table", table); + }; + } + + Y_UNIT_TEST(RestoreTableContent) { + TS3TestEnv testEnv; + constexpr const char* table = "/Root/table"; + + TestTableContentIsPreserved( + table, + testEnv.GetSession(), + CreateBackupLambda(testEnv.GetDriver(), testEnv.GetS3Port()), + CreateRestoreLambda(testEnv.GetDriver(), testEnv.GetS3Port()) + ); + } + + Y_UNIT_TEST(RestoreTablePartitioningSettings) { + TS3TestEnv testEnv; + constexpr const char* table = "/Root/table"; + constexpr ui32 minPartitions = 10; + + TestTablePartitioningSettingsArePreserved( + table, + minPartitions, + testEnv.GetSession(), + CreateBackupLambda(testEnv.GetDriver(), testEnv.GetS3Port()), + CreateRestoreLambda(testEnv.GetDriver(), testEnv.GetS3Port()) + ); + } + + Y_UNIT_TEST(RestoreIndexTablePartitioningSettings) { + TS3TestEnv testEnv; + constexpr const char* table = "/Root/table"; + constexpr const char* index = "byValue"; + constexpr ui32 minIndexPartitions = 10; + + TestIndexTablePartitioningSettingsArePreserved( + table, + index, + minIndexPartitions, + testEnv.GetSession(), + CreateBackupLambda(testEnv.GetDriver(), testEnv.GetS3Port()), + CreateRestoreLambda(testEnv.GetDriver(), testEnv.GetS3Port()) + ); + } + + Y_UNIT_TEST(RestoreTableSplitBoundaries) { + TS3TestEnv testEnv; + constexpr const char* table = "/Root/table"; + constexpr ui64 partitions = 10; + + TestTableSplitBoundariesArePreserved( + table, + partitions, + testEnv.GetSession(), + CreateBackupLambda(testEnv.GetDriver(), testEnv.GetS3Port()), + CreateRestoreLambda(testEnv.GetDriver(), testEnv.GetS3Port()) + ); + } + + Y_UNIT_TEST(RestoreIndexTableSplitBoundaries) { + TS3TestEnv testEnv; + constexpr const char* table = "/Root/table"; + constexpr const char* index = "byValue"; + constexpr ui64 indexPartitions = 10; + + TestIndexTableSplitBoundariesArePreserved( + table, + index, + indexPartitions, + testEnv.GetSession(), + CreateBackupLambda(testEnv.GetDriver(), testEnv.GetS3Port()), + CreateRestoreLambda(testEnv.GetDriver(), testEnv.GetS3Port()) + ); + } + +} diff --git a/ydb/services/ydb/ut/ya.make b/ydb/services/ydb/ut/ya.make index 2d60535a3205..09601dd06b5e 100644 --- a/ydb/services/ydb/ut/ya.make +++ b/ydb/services/ydb/ut/ya.make @@ -42,8 +42,6 @@ PEERDIR( ydb/core/grpc_services/base ydb/core/testlib ydb/core/security - ydb/core/wrappers/ut_helpers - ydb/library/backup ydb/library/yql/minikql/dom ydb/library/yql/minikql/jsonpath ydb/library/testlib/service_mocks/ldap_mock @@ -52,7 +50,6 @@ PEERDIR( ydb/public/lib/yson_value ydb/public/lib/ut_helpers ydb/public/lib/ydb_cli/commands - ydb/public/lib/ydb_cli/dump ydb/public/sdk/cpp/client/draft ydb/public/sdk/cpp/client/ydb_coordination ydb/public/sdk/cpp/client/ydb_export diff --git a/ydb/services/ydb/ya.make b/ydb/services/ydb/ya.make index d42bbf19283c..34676794495e 100644 --- a/ydb/services/ydb/ya.make +++ b/ydb/services/ydb/ya.make @@ -37,6 +37,7 @@ PEERDIR( END() RECURSE_FOR_TESTS( + backup_ut sdk_sessions_ut sdk_sessions_pool_ut table_split_ut diff --git a/ydb/services/ydb/ydb_import_ut.cpp b/ydb/services/ydb/ydb_import_ut.cpp index 3c93e2b57d23..961cd9679c85 100644 --- a/ydb/services/ydb/ydb_import_ut.cpp +++ b/ydb/services/ydb/ydb_import_ut.cpp @@ -3,10 +3,8 @@ #include #include #include -#include #include -#include #include #include @@ -130,272 +128,3 @@ Y_UNIT_TEST_SUITE(YdbImport) { } } - -Y_UNIT_TEST_SUITE(BackupRestore) { - - using namespace NYdb::NTable; - - void ExecuteDataDefinitionQuery(TSession& session, const TString& script) { - const auto result = session.ExecuteSchemeQuery(script).ExtractValueSync(); - UNIT_ASSERT_C(result.IsSuccess(), "script:\n" << script << "\nissues:\n" << result.GetIssues().ToString()); - } - - TDataQueryResult ExecuteDataModificationQuery(TSession& session, - const TString& script, - const TExecDataQuerySettings& settings = {} - ) { - const auto result = session.ExecuteDataQuery( - script, - TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(), - settings - ).ExtractValueSync(); - UNIT_ASSERT_C(result.IsSuccess(), "script:\n" << script << "\nissues:\n" << result.GetIssues().ToString()); - - return result; - } - - TValue GetSingleResult(const TDataQueryResult& rawResults) { - auto resultSetParser = rawResults.GetResultSetParser(0); - UNIT_ASSERT(resultSetParser.TryNextRow()); - return resultSetParser.GetValue(0); - } - - ui64 GetUint64(const TValue& value) { - return TValueParser(value).GetUint64(); - } - - void Restore(NDump::TClient& client, const TFsPath& sourceFile, const TString& dbPath) { - auto result = client.Restore(sourceFile, dbPath); - UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); - } - - auto CreateMinPartitionsChecker(ui64 expectedMinPartitions) { - return [=](const TTableDescription& tableDescription) { - return tableDescription.GetPartitioningSettings().GetMinPartitionsCount() == expectedMinPartitions; - }; - } - - void CheckTableDescription(TSession& session, const TString& path, auto&& checker) { - auto describeResult = session.DescribeTable(path, TDescribeTableSettings().WithSetVal(true)).ExtractValueSync(); - UNIT_ASSERT_C(describeResult.IsSuccess(), describeResult.GetIssues().ToString()); - auto tableDescription = describeResult.GetTableDescription(); - Ydb::Table::CreateTableRequest descriptionProto; - // The purpose of translating to CreateTableRequest is solely to produce a clearer error message. - tableDescription.SerializeTo(descriptionProto); - UNIT_ASSERT_C( - checker(tableDescription), - descriptionProto.DebugString() - ); - } - - auto CreateHasSerialChecker(i64 nextValue, bool nextUsed) { - return [=](const TTableDescription& tableDescription) { - for (const auto& column : tableDescription.GetTableColumns()) { - if (column.Name == "Key") { - UNIT_ASSERT(column.SequenceDescription.has_value()); - UNIT_ASSERT(column.SequenceDescription->SetVal.has_value()); - UNIT_ASSERT_VALUES_EQUAL(column.SequenceDescription->SetVal->NextValue, nextValue); - UNIT_ASSERT_VALUES_EQUAL(column.SequenceDescription->SetVal->NextUsed, nextUsed); - return true; - } - } - return false; - }; - } - - Y_UNIT_TEST(Basic) { - TKikimrWithGrpcAndRootSchema server; - auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%d", server.GetPort()))); - TTableClient tableClient(driver); - auto session = tableClient.GetSession().ExtractValueSync().GetSession(); - - constexpr const char* table = "/Root/table"; - ExecuteDataDefinitionQuery(session, Sprintf(R"( - CREATE TABLE `%s` ( - Key Uint32, - Value Utf8, - PRIMARY KEY (Key) - ); - )", - table - )); - ExecuteDataModificationQuery(session, Sprintf(R"( - UPSERT INTO `%s` ( - Key, - Value - ) - VALUES - (1, "one"), - (2, "two"), - (3, "three"), - (4, "four"), - (5, "five"); - )", - table - )); - - TTempDir tempDir; - const auto& pathToBackup = tempDir.Path(); - // TO DO: implement NDump::TClient::Dump and call it instead of BackupFolder - NYdb::NBackup::BackupFolder(driver, "/Root", ".", pathToBackup, {}, false, false); - - NDump::TClient backupClient(driver); - - // restore deleted rows in an existing table - ExecuteDataModificationQuery(session, Sprintf(R"( - DELETE FROM `%s` WHERE Key > 3; - )", table - )); - Restore(backupClient, pathToBackup, "/Root"); - { - auto result = ExecuteDataModificationQuery(session, Sprintf(R"( - SELECT COUNT(*) FROM `%s`; - )", table - )); - UNIT_ASSERT_VALUES_EQUAL(GetUint64(GetSingleResult(result)), 5ull); - } - - // restore deleted table - ExecuteDataDefinitionQuery(session, Sprintf(R"( - DROP TABLE `%s`; - )", table - )); - Restore(backupClient, pathToBackup, "/Root"); - { - auto result = ExecuteDataModificationQuery(session, Sprintf(R"( - SELECT COUNT(*) FROM `%s`; - )", table - )); - UNIT_ASSERT_VALUES_EQUAL(GetUint64(GetSingleResult(result)), 5ull); - } - } - - Y_UNIT_TEST(RestoreTablePartitioningSettings) { - TKikimrWithGrpcAndRootSchema server; - auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%d", server.GetPort()))); - TTableClient tableClient(driver); - auto session = tableClient.GetSession().ExtractValueSync().GetSession(); - - constexpr const char* table = "/Root/table"; - constexpr int minPartitions = 10; - ExecuteDataDefinitionQuery(session, Sprintf(R"( - CREATE TABLE `%s` ( - Key Uint32, - Value Utf8, - PRIMARY KEY (Key) - ) - WITH ( - AUTO_PARTITIONING_BY_LOAD = ENABLED, - AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = %d - ); - )", - table, minPartitions - )); - - CheckTableDescription(session, table, CreateMinPartitionsChecker(minPartitions)); - - TTempDir tempDir; - const auto& pathToBackup = tempDir.Path(); - // TO DO: implement NDump::TClient::Dump and call it instead of BackupFolder - NYdb::NBackup::BackupFolder(driver, "/Root", ".", pathToBackup, {}, false, false); - - NDump::TClient backupClient(driver); - - // restore deleted table - ExecuteDataDefinitionQuery(session, Sprintf(R"( - DROP TABLE `%s`; - )", table - )); - Restore(backupClient, pathToBackup, "/Root"); - CheckTableDescription(session, table, CreateMinPartitionsChecker(minPartitions)); - } - - Y_UNIT_TEST(RestoreIndexTablePartitioningSettings) { - TKikimrWithGrpcAndRootSchema server; - auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%d", server.GetPort()))); - TTableClient tableClient(driver); - auto session = tableClient.GetSession().ExtractValueSync().GetSession(); - - constexpr const char* table = "/Root/table"; - constexpr const char* index = "byValue"; - const TString indexTablePath = JoinFsPaths(table, index, "indexImplTable"); - constexpr int minPartitions = 10; - ExecuteDataDefinitionQuery(session, Sprintf(R"( - CREATE TABLE `%s` ( - Key Uint32, - Value Uint32, - PRIMARY KEY (Key), - INDEX %s GLOBAL ON (Value) - ); - )", - table, index - )); - ExecuteDataDefinitionQuery(session, Sprintf(R"( - ALTER TABLE `%s` ALTER INDEX %s SET ( - AUTO_PARTITIONING_BY_LOAD = ENABLED, - AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = %d - ); - )", table, index, minPartitions - )); - - CheckTableDescription(session, indexTablePath, CreateMinPartitionsChecker(minPartitions)); - - TTempDir tempDir; - const auto& pathToBackup = tempDir.Path(); - // TO DO: implement NDump::TClient::Dump and call it instead of BackupFolder - NYdb::NBackup::BackupFolder(driver, "/Root", ".", pathToBackup, {}, false, false); - - NDump::TClient backupClient(driver); - - // restore deleted table - ExecuteDataDefinitionQuery(session, Sprintf(R"( - DROP TABLE `%s`; - )", table - )); - Restore(backupClient, pathToBackup, "/Root"); - CheckTableDescription(session, indexTablePath, CreateMinPartitionsChecker(minPartitions)); - } - - Y_UNIT_TEST(BasicRestoreTableWithSerial) { - TKikimrWithGrpcAndRootSchema server; - auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%d", server.GetPort()))); - TTableClient tableClient(driver); - auto session = tableClient.GetSession().ExtractValueSync().GetSession(); - - constexpr const char* table = "/Root/table"; - - ExecuteDataDefinitionQuery(session, Sprintf(R"( - CREATE TABLE `%s` ( - Key Serial, - Value Uint32, - PRIMARY KEY (Key) - ); - )", - table - )); - ExecuteDataModificationQuery(session, Sprintf(R"( - UPSERT INTO `%s` ( - Value - ) - VALUES (1), (2), (3), (4), (5), (6), (7); - )", - table - )); - - TTempDir tempDir; - const auto& pathToBackup = tempDir.Path(); - // TO DO: implement NDump::TClient::Dump and call it instead of BackupFolder - NYdb::NBackup::BackupFolder(driver, "/Root", ".", pathToBackup, {}, false, false); - - NDump::TClient backupClient(driver); - - // restore deleted table - ExecuteDataDefinitionQuery(session, Sprintf(R"( - DROP TABLE `%s`; - )", table - )); - Restore(backupClient, pathToBackup, "/Root"); - CheckTableDescription(session, table, CreateHasSerialChecker(8, false)); - } - -} diff --git a/ydb/services/ymq/ymq_proxy.cpp b/ydb/services/ymq/ymq_proxy.cpp index 68c85647c315..6a60db3179f8 100644 --- a/ydb/services/ymq/ymq_proxy.cpp +++ b/ydb/services/ymq/ymq_proxy.cpp @@ -321,29 +321,26 @@ namespace NKikimr::NYmq::V1 { for (const auto& srcMessage: GetResponse(resp).GetMessages()) { Ydb::Ymq::V1::Message dstMessage; - for (TString& attributeName : AttributesNames) { - if (attributeName == APPROXIMATE_RECEIVE_COUNT) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetApproximateReceiveCount()); - } else if (attributeName == APPROXIMATE_FIRST_RECEIVE_TIMESTAMP) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetApproximateFirstReceiveTimestamp()); - } else if (attributeName == MESSAGE_DEDUPLICATION_ID) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetMessageDeduplicationId()); - } else if (attributeName == MESSAGE_GROUP_ID) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetMessageGroupId()); - } else if (attributeName == SENDER_ID) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetSenderId()); - } else if (attributeName == SENT_TIMESTAMP) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetSentTimestamp()); - } else if (attributeName == SEQUENCE_NUMBER) { - dstMessage.Mutableattributes()->at(attributeName) - .assign(srcMessage.GetSequenceNumber()); - } + if (srcMessage.HasApproximateReceiveCount()) { + dstMessage.Mutableattributes()->insert({APPROXIMATE_RECEIVE_COUNT, std::to_string(srcMessage.GetApproximateReceiveCount())}); + } + if (srcMessage.HasApproximateFirstReceiveTimestamp()) { + dstMessage.Mutableattributes()->insert({APPROXIMATE_FIRST_RECEIVE_TIMESTAMP, std::to_string(srcMessage.GetApproximateFirstReceiveTimestamp())}); + } + if (srcMessage.HasMessageDeduplicationId()) { + dstMessage.Mutableattributes()->insert({MESSAGE_DEDUPLICATION_ID, srcMessage.GetMessageDeduplicationId()}); + } + if (srcMessage.HasMessageGroupId()) { + dstMessage.Mutableattributes()->insert({MESSAGE_GROUP_ID, srcMessage.GetMessageGroupId()}); + } + if (srcMessage.HasSenderId()) { + dstMessage.Mutableattributes()->insert({SENDER_ID, srcMessage.GetSenderId()}); + } + if (srcMessage.HasSentTimestamp()) { + dstMessage.Mutableattributes()->insert({SENT_TIMESTAMP, std::to_string(srcMessage.GetSentTimestamp())}); + } + if (srcMessage.HasSequenceNumber()) { + dstMessage.Mutableattributes()->insert({SEQUENCE_NUMBER, std::to_string(srcMessage.GetSequenceNumber())}); } dstMessage.set_body(srcMessage.GetData()); @@ -391,17 +388,17 @@ namespace NKikimr::NYmq::V1 { // because AttributeNames is deprecated in favour of SystemAttributeNames if (systemAttributeNames.size() > 0) { for (int i = 0; i < systemAttributeNames.size(); i++) { - result->SetAttributeName(i, systemAttributeNames[i]); + result->AddAttributeName(systemAttributeNames[i]); } } else { auto attributeNames = GetProtoRequest()->Getattribute_names(); for (int i = 0; i < attributeNames.size(); i++) { - result->SetAttributeName(i, attributeNames[i]); + result->AddAttributeName(attributeNames[i]); } } for (int i = 0; i < GetProtoRequest()->Getmessage_attribute_names().size(); i++) { - result->SetMessageAttributeName(i, GetProtoRequest()->Getmessage_attribute_names()[i]); + result->AddMessageAttributeName(GetProtoRequest()->Getmessage_attribute_names()[i]); } return result; diff --git a/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema b/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema index 02b8cd6f6188..a9352a6e571d 100644 --- a/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema +++ b/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema @@ -7313,6 +7313,11 @@ "ColumnId": 3, "ColumnName": "QueryText", "ColumnType": "String" + }, + { + "ColumnId": 4, + "ColumnName": "CapturedContext", + "ColumnType": "String" } ], "ColumnsDropped": [], @@ -7321,7 +7326,8 @@ "Columns": [ 1, 2, - 3 + 3, + 4 ], "RoomID": 0, "Codec": 0,