diff --git a/ydb/core/kqp/common/events/workload_service.h b/ydb/core/kqp/common/events/workload_service.h index 514579bfcb24..c1d36a957a76 100644 --- a/ydb/core/kqp/common/events/workload_service.h +++ b/ydb/core/kqp/common/events/workload_service.h @@ -12,6 +12,16 @@ namespace NKikimr::NKqp::NWorkload { +struct TEvSubscribeOnPoolChanges : public NActors::TEventLocal { + TEvSubscribeOnPoolChanges(const TString& database, const TString& poolId) + : Database(database) + , PoolId(poolId) + {} + + const TString Database; + const TString PoolId; +}; + struct TEvPlaceRequestIntoPool : public NActors::TEventLocal { TEvPlaceRequestIntoPool(const TString& database, const TString& sessionId, const TString& poolId, TIntrusiveConstPtr userToken) : Database(database) @@ -80,4 +90,14 @@ struct TEvUpdatePoolInfo : public NActors::TEventLocal SecurityObject; }; +struct TEvUpdateDatabaseInfo : public NActors::TEventLocal { + TEvUpdateDatabaseInfo(const TString& database, bool serverless) + : Database(database) + , Serverless(serverless) + {} + + const TString Database; + const bool Serverless; +}; + } // NKikimr::NKqp::NWorkload diff --git a/ydb/core/kqp/common/simple/kqp_event_ids.h b/ydb/core/kqp/common/simple/kqp_event_ids.h index b0002f332bd2..8c4b3fcab29a 100644 --- a/ydb/core/kqp/common/simple/kqp_event_ids.h +++ b/ydb/core/kqp/common/simple/kqp_event_ids.h @@ -175,6 +175,8 @@ struct TKqpWorkloadServiceEvents { EvCleanupRequest, EvCleanupResponse, EvUpdatePoolInfo, + EvUpdateDatabaseInfo, + EvSubscribeOnPoolChanges, }; }; diff --git a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp index 1f94121d4eb2..3df3fc2f3ac7 100644 --- a/ydb/core/kqp/compile_service/kqp_compile_actor.cpp +++ b/ydb/core/kqp/compile_service/kqp_compile_actor.cpp @@ -276,7 +276,7 @@ class TKqpCompileActor : public TActorBootstrapped { KqpHost = CreateKqpHost(Gateway, QueryId.Cluster, QueryId.Database, Config, ModuleResolverState->ModuleResolver, FederatedQuerySetup, UserToken, GUCSettings, QueryServiceConfig, ApplicationName, AppData(ctx)->FunctionRegistry, - false, false, std::move(TempTablesState), nullptr, SplitCtx); + false, false, std::move(TempTablesState), nullptr, SplitCtx, UserRequestContext); IKqpHost::TPrepareSettings prepareSettings; prepareSettings.DocumentApiRestricted = QueryId.Settings.DocumentApiRestricted; diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool/manager.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool/manager.cpp index d3196d1f2f85..cbc6f50e4f53 100644 --- a/ydb/core/kqp/gateway/behaviour/resource_pool/manager.cpp +++ b/ydb/core/kqp/gateway/behaviour/resource_pool/manager.cpp @@ -118,10 +118,10 @@ void FillResourcePoolDescription(NKikimrSchemeOp::TResourcePoolDescription& reso TPoolSettings resourcePoolSettings; auto& properties = *resourcePoolDescription.MutableProperties()->MutableProperties(); - for (const auto& [property, setting] : GetPropertiesMap(resourcePoolSettings, true)) { + for (const auto& [property, setting] : resourcePoolSettings.GetPropertiesMap(true)) { if (std::optional value = featuresExtractor.Extract(property)) { try { - std::visit(TSettingsParser{*value}, setting); + std::visit(TPoolSettings::TParser{*value}, setting); } catch (...) { throw yexception() << "Failed to parse property " << property << ": " << CurrentExceptionMessage(); } @@ -129,7 +129,7 @@ void FillResourcePoolDescription(NKikimrSchemeOp::TResourcePoolDescription& reso continue; } - TString value = std::visit(TSettingsExtractor(), setting); + const TString value = std::visit(TPoolSettings::TExtractor(), setting); properties.insert({property, value}); } @@ -241,7 +241,7 @@ void TResourcePoolManager::PrepareCreateResourcePool(NKqpProto::TKqpSchemeOperat } auto& schemeTx = *schemeOperation.MutableCreateResourcePool(); - schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".resource_pools/"})); + schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".metadata/workload_manager/pools/"})); schemeTx.SetOperationType(NKikimrSchemeOp::ESchemeOpCreateResourcePool); FillResourcePoolDescription(*schemeTx.MutableCreateResourcePool(), settings); @@ -249,7 +249,7 @@ void TResourcePoolManager::PrepareCreateResourcePool(NKqpProto::TKqpSchemeOperat void TResourcePoolManager::PrepareAlterResourcePool(NKqpProto::TKqpSchemeOperation& schemeOperation, const NYql::TDropObjectSettings& settings, TInternalModificationContext& context) const { auto& schemeTx = *schemeOperation.MutableAlterResourcePool(); - schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".resource_pools/"})); + schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".metadata/workload_manager/pools/"})); schemeTx.SetOperationType(NKikimrSchemeOp::ESchemeOpAlterResourcePool); FillResourcePoolDescription(*schemeTx.MutableCreateResourcePool(), settings); @@ -257,7 +257,7 @@ void TResourcePoolManager::PrepareAlterResourcePool(NKqpProto::TKqpSchemeOperati void TResourcePoolManager::PrepareDropResourcePool(NKqpProto::TKqpSchemeOperation& schemeOperation, const NYql::TDropObjectSettings& settings, TInternalModificationContext& context) const { auto& schemeTx = *schemeOperation.MutableDropResourcePool(); - schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".resource_pools/"})); + schemeTx.SetWorkingDir(JoinPath({context.GetExternalData().GetDatabase(), ".metadata/workload_manager/pools/"})); schemeTx.SetOperationType(NKikimrSchemeOp::ESchemeOpDropResourcePool); schemeTx.MutableDrop()->SetName(settings.GetObjectId()); diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.cpp new file mode 100644 index 000000000000..aad9f7831007 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.cpp @@ -0,0 +1,31 @@ +#include "behaviour.h" +#include "initializer.h" +#include "manager.h" + + +namespace NKikimr::NKqp { + +TResourcePoolClassifierBehaviour::TFactory::TRegistrator TResourcePoolClassifierBehaviour::Registrator(TResourcePoolClassifierConfig::GetTypeId()); + +NMetadata::NInitializer::IInitializationBehaviour::TPtr TResourcePoolClassifierBehaviour::ConstructInitializer() const { + return std::make_shared(); +} + +NMetadata::NModifications::IOperationsManager::TPtr TResourcePoolClassifierBehaviour::ConstructOperationsManager() const { + return std::make_shared(); +} + +TString TResourcePoolClassifierBehaviour::GetInternalStorageTablePath() const { + return "workload_manager/classifiers/resource_pool_classifiers"; +} + +TString TResourcePoolClassifierBehaviour::GetTypeId() const { + return TResourcePoolClassifierConfig::GetTypeId(); +} + +NMetadata::IClassBehaviour::TPtr TResourcePoolClassifierBehaviour::GetInstance() { + static std::shared_ptr result = std::make_shared(); + return result; +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.h new file mode 100644 index 000000000000..42c8440f80ed --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/behaviour.h @@ -0,0 +1,25 @@ +#pragma once + +#include "object.h" + +#include +#include + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierBehaviour : public NMetadata::TClassBehaviour { + static TFactory::TRegistrator Registrator; + +protected: + virtual NMetadata::NInitializer::IInitializationBehaviour::TPtr ConstructInitializer() const override; + virtual NMetadata::NModifications::IOperationsManager::TPtr ConstructOperationsManager() const override; + virtual TString GetInternalStorageTablePath() const override; + +public: + virtual TString GetTypeId() const override; + + static IClassBehaviour::TPtr GetInstance(); +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.cpp new file mode 100644 index 000000000000..5b6bae22b411 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.cpp @@ -0,0 +1,329 @@ +#include "checker.h" +#include "fetcher.h" + +#include +#include +#include +#include +#include +#include + +#include + + +namespace NKikimr::NKqp { + +namespace { + +using namespace NActors; +using namespace NResourcePool; +using namespace NWorkload; + + +class TRanksCheckerActor : public NKikimr::TQueryBase { + using TBase = NKikimr::TQueryBase; + +public: + TRanksCheckerActor(const TString& database, const TString& sessionId, const TString& transactionId, const std::unordered_map& ranksToCheck) + : TBase(NKikimrServices::KQP_GATEWAY, sessionId) + , Database(database) + , RanksToCheck(ranksToCheck) + { + TxId = transactionId; + SetOperationInfo(__func__, Database); + } + + void OnRunQuery() override { + const auto& tablePath = TResourcePoolClassifierConfig::GetBehaviour()->GetStorageTablePath(); + + TStringBuilder sql = TStringBuilder() << R"( + -- TRanksCheckerActor::OnRunQuery + DECLARE $database AS Text; + )"; + + NYdb::TParamsBuilder params; + params + .AddParam("$database") + .Utf8(CanonizePath(Database)) + .Build(); + + if (!RanksToCheck.empty()) { + sql << R"( + DECLARE $ranks AS List; + PRAGMA AnsiInForEmptyOrNullableItemsCollections; + + SELECT + rank, name + FROM `)" << tablePath << R"(` + WHERE database = $database + AND rank IN $ranks; + )"; + + auto& param = params.AddParam("$ranks").BeginList(); + for (const auto& [rank, _] : RanksToCheck) { + param.AddListItem().Int64(rank); + } + param.EndList().Build(); + + ExpectedResultSets++; + } + + sql << R"( + SELECT + MAX(rank) AS MaxRank, + COUNT(*) AS NumberClassifiers + FROM `)" << tablePath << R"(` + WHERE database = $database; + )"; + + RunDataQuery(sql, ¶ms, TTxControl::ContinueTx()); + } + + void OnQueryResult() override { + if (ResultSets.size() != ExpectedResultSets) { + Finish(Ydb::StatusIds::INTERNAL_ERROR, "Unexpected database response"); + return; + } + + ui64 resultSetId = 0; + if (!RanksToCheck.empty()) { + NYdb::TResultSetParser result(ResultSets[resultSetId++]); + while (result.TryNextRow()) { + TMaybe rank = result.ColumnParser("rank").GetOptionalInt64(); + if (!rank) { + continue; + } + + TMaybe name = result.ColumnParser("name").GetOptionalUtf8(); + if (!name) { + continue; + } + + if (auto it = RanksToCheck.find(*rank); it != RanksToCheck.end() && it->second != *name) { + Finish(Ydb::StatusIds::ALREADY_EXISTS, TStringBuilder() << "Classifier with rank " << *rank << " already exists, its name " << *name); + return; + } + } + } + + { // Classifiers stats + NYdb::TResultSetParser result(ResultSets[resultSetId++]); + if (!result.TryNextRow()) { + Finish(Ydb::StatusIds::INTERNAL_ERROR, "Unexpected database response"); + return; + } + + MaxRank = result.ColumnParser("MaxRank").GetOptionalInt64().GetOrElse(0); + NumberClassifiers = result.ColumnParser("NumberClassifiers").GetUint64(); + } + + Finish(); + } + + void OnFinish(Ydb::StatusIds::StatusCode status, NYql::TIssues&& issues) override { + Send(Owner, new TEvPrivate::TEvRanksCheckerResponse(status, MaxRank, NumberClassifiers, std::move(issues))); + } + +private: + const TString Database; + const std::unordered_map RanksToCheck; + + ui64 ExpectedResultSets = 1; + i64 MaxRank = 0; + ui64 NumberClassifiers = 0; +}; + +class TResourcePoolClassifierPreparationActor : public TActorBootstrapped { +public: + TResourcePoolClassifierPreparationActor(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) + : Context(context) + , AlterContext(alterContext) + , Controller(std::move(controller)) + , PatchedObjects(std::move(patchedObjects)) + {} + + void Bootstrap() { + Become(&TResourcePoolClassifierPreparationActor::StateFunc); + ValidateRanks(); + GetDatabaseInfo(); + } + + void Handle(TEvPrivate::TEvRanksCheckerResponse::TPtr& ev) { + if (ev->Get()->Status != Ydb::StatusIds::SUCCESS) { + FailAndPassAway("Resource pool classifier rank check failed", ev->Get()->Status, ev->Get()->Issues); + return; + } + + if (Context.GetActivityType() == NMetadata::NModifications::IOperationsManager::EActivityType::Create && ev->Get()->NumberClassifiers >= CLASSIFIER_COUNT_LIMIT) { + FailAndPassAway(TStringBuilder() << "Number of resource pool classifiers reached limit in " << CLASSIFIER_COUNT_LIMIT); + return; + } + + i64 maxRank = ev->Get()->MaxRank; + for (auto& object : PatchedObjects) { + if (object.GetRank() != -1) { + continue; + } + if (maxRank > std::numeric_limits::max() - CLASSIFIER_RANK_OFFSET) { + FailAndPassAway(TStringBuilder() << "The rank could not be set automatically, the maximum rank of the resource pool classifier is too high: " << ev->Get()->MaxRank); + return; + } + + maxRank += CLASSIFIER_RANK_OFFSET; + object.SetRank(maxRank); + } + + RanksChecked = true; + TryFinish(); + } + + void Handle(TEvPrivate::TEvFetchDatabaseResponse::TPtr& ev) { + if (ev->Get()->Status != Ydb::StatusIds::SUCCESS) { + FailAndPassAway("Database check failed", ev->Get()->Status, ev->Get()->Issues); + return; + } + + Serverless = ev->Get()->Serverless; + + Send(NConsole::MakeConfigsDispatcherID(SelfId().NodeId()), new NConsole::TEvConfigsDispatcher::TEvGetConfigRequest( + (ui32)NKikimrConsole::TConfigItem::FeatureFlagsItem + ), IEventHandle::FlagTrackDelivery); + } + + void Handle(TEvents::TEvUndelivered::TPtr& ev) { + switch (ev->Get()->SourceType) { + case NConsole::TEvConfigsDispatcher::EvGetConfigRequest: + CheckFeatureFlag(AppData()->FeatureFlags); + break; + + default: + break; + } + } + + void Handle(NConsole::TEvConfigsDispatcher::TEvGetConfigResponse::TPtr& ev) { + CheckFeatureFlag(ev->Get()->Config->GetFeatureFlags()); + } + + void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { + const auto& snapshot = ev->Get()->GetSnapshotAs(); + for (const auto& objectRecord : AlterContext.GetRestoreObjectIds().GetTableRecords()) { + TResourcePoolClassifierConfig object; + TResourcePoolClassifierConfig::TDecoder::DeserializeFromRecord(object, objectRecord); + + if (!snapshot->GetClassifierConfig(CanonizePath(object.GetDatabase()), object.GetName())) { + FailAndPassAway(TStringBuilder() << "Classifier with name " << object.GetName() << " not found in database " << object.GetDatabase()); + return; + } + } + + ExistenceChecked = true; + TryFinish(); + } + + STRICT_STFUNC(StateFunc, + hFunc(TEvPrivate::TEvRanksCheckerResponse, Handle); + hFunc(TEvPrivate::TEvFetchDatabaseResponse, Handle); + hFunc(TEvents::TEvUndelivered, Handle); + hFunc(NConsole::TEvConfigsDispatcher::TEvGetConfigResponse, Handle); + hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle) + ) + +private: + void GetDatabaseInfo() const { + const auto& externalContext = Context.GetExternalData(); + const auto userToken = externalContext.GetUserToken() ? MakeIntrusive(*externalContext.GetUserToken()) : nullptr; + Register(CreateDatabaseFetcherActor(SelfId(), externalContext.GetDatabase(), userToken, NACLib::EAccessRights::GenericFull)); + } + + void ValidateRanks() { + if (Context.GetActivityType() == NMetadata::NModifications::IOperationsManager::EActivityType::Drop) { + RanksChecked = true; + TryFinish(); + return; + } + + std::unordered_map ranksToNames; + for (const auto& object : PatchedObjects) { + const auto rank = object.GetRank(); + if (rank == -1) { + continue; + } + if (!ranksToNames.insert({rank, object.GetName()}).second) { + FailAndPassAway(TStringBuilder() << "Found duplicate rank " << rank); + } + } + + Register(new TQueryRetryActor>( + SelfId(), Context.GetExternalData().GetDatabase(), AlterContext.GetSessionId(), AlterContext.GetTransactionId(), ranksToNames + )); + } + + void CheckFeatureFlag(const NKikimrConfig::TFeatureFlags& featureFlags) { + if (Context.GetActivityType() == NMetadata::NModifications::IOperationsManager::EActivityType::Drop) { + FeatureFlagChecked = true; + ValidateExistence(); + return; + } + + if (!featureFlags.GetEnableResourcePools()) { + FailAndPassAway("Resource pool classifiers are disabled. Please contact your system administrator to enable it"); + return; + } + if (Serverless && !featureFlags.GetEnableResourcePoolsOnServerless()) { + FailAndPassAway("Resource pool classifiers are disabled for serverless domains. Please contact your system administrator to enable it"); + return; + } + + FeatureFlagChecked = true; + ValidateExistence(); + } + + void ValidateExistence() { + if (Context.GetActivityType() != NMetadata::NModifications::IOperationsManager::EActivityType::Create && NMetadata::NProvider::TServiceOperator::IsEnabled()) { + Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); + return; + } + + ExistenceChecked = true; + TryFinish(); + } + + void FailAndPassAway(const TString& message, Ydb::StatusIds::StatusCode status, NYql::TIssues issues) { + FailAndPassAway(TStringBuilder() << message << ", status: " << status << ", reason: " << issues.ToOneLineString()); + } + + void FailAndPassAway(const TString& message) { + Controller->OnPreparationProblem(message); + PassAway(); + } + + void TryFinish() { + if (!FeatureFlagChecked || !RanksChecked || !ExistenceChecked) { + return; + } + + Controller->OnPreparationFinished(std::move(PatchedObjects)); + PassAway(); + } + +private: + const NMetadata::NModifications::IOperationsManager::TInternalModificationContext Context; + const NMetadata::NModifications::TAlterOperationContext AlterContext; + + bool Serverless = false; + bool FeatureFlagChecked = false; + bool RanksChecked = false; + bool ExistenceChecked = false; + + NMetadata::NModifications::IAlterPreparationController::TPtr Controller; + std::vector PatchedObjects; +}; + +} // anonymous namespace + +IActor* CreateResourcePoolClassifierPreparationActor(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) { + return new TResourcePoolClassifierPreparationActor(std::move(patchedObjects), std::move(controller), context, alterContext); +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.h new file mode 100644 index 000000000000..3018087f7553 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/checker.h @@ -0,0 +1,12 @@ +#pragma once + +#include "object.h" + +#include + + +namespace NKikimr::NKqp { + +NActors::IActor* CreateResourcePoolClassifierPreparationActor(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext); + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.cpp new file mode 100644 index 000000000000..e9f7f3d59bb0 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.cpp @@ -0,0 +1,10 @@ +#include "fetcher.h" + + +namespace NKikimr::NKqp { + +std::vector TResourcePoolClassifierSnapshotsFetcher::DoGetManagers() const { + return {TResourcePoolClassifierConfig::GetBehaviour()}; +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.h new file mode 100644 index 000000000000..29611f0cbeb0 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/fetcher.h @@ -0,0 +1,13 @@ +#pragma once + +#include "snapshot.h" + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierSnapshotsFetcher : public NMetadata::NFetcher::TSnapshotsFetcher { +protected: + virtual std::vector DoGetManagers() const override; +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.cpp new file mode 100644 index 000000000000..39230296e4be --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.cpp @@ -0,0 +1,41 @@ +#include "initializer.h" +#include "object.h" + + +namespace NKikimr::NKqp { + +namespace { + +void AddColumn(Ydb::Table::CreateTableRequest& request, const TString& name, Ydb::Type::PrimitiveTypeId type, bool primary = false) { + if (primary) { + request.add_primary_key(name); + } + + auto& column = *request.add_columns(); + column.set_name(name); + column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(type); +} + +} // anonymous namespace + +void TResourcePoolClassifierInitializer::DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const { + TVector result; + { + Ydb::Table::CreateTableRequest request; + request.set_session_id(""); + request.set_path(TResourcePoolClassifierConfig::GetBehaviour()->GetStorageTablePath()); + AddColumn(request, TResourcePoolClassifierConfig::TDecoder::Database, Ydb::Type::UTF8, true); + AddColumn(request, TResourcePoolClassifierConfig::TDecoder::Name, Ydb::Type::UTF8, true); + AddColumn(request, TResourcePoolClassifierConfig::TDecoder::Rank, Ydb::Type::INT64); + AddColumn(request, TResourcePoolClassifierConfig::TDecoder::ConfigJson, Ydb::Type::JSON_DOCUMENT); + result.emplace_back(std::make_shared>(request, "create")); + + auto historyRequest = TResourcePoolClassifierConfig::AddHistoryTableScheme(request); + result.emplace_back(std::make_shared>(historyRequest, "create_history")); + } + result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TResourcePoolClassifierConfig::GetBehaviour()->GetStorageTablePath(), "acl")); + result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TResourcePoolClassifierConfig::GetBehaviour()->GetStorageHistoryTablePath(), "acl_history")); + controller->OnPreparationFinished(result); +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.h new file mode 100644 index 000000000000..c1743cfcbcfd --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/initializer.h @@ -0,0 +1,13 @@ +#pragma once + +#include + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierInitializer : public NMetadata::NInitializer::IInitializationBehaviour { +protected: + virtual void DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const override; +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.cpp new file mode 100644 index 000000000000..dc884cd029ba --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.cpp @@ -0,0 +1,110 @@ +#include "manager.h" +#include "checker.h" + +#include +#include + + +namespace NKikimr::NKqp { + +namespace { + +using namespace NResourcePool; + +NMetadata::NInternal::TTableRecord GetResourcePoolClassifierRecord(const NYql::TObjectSettingsImpl& settings, const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context) { + NMetadata::NInternal::TTableRecord result; + result.SetColumn(TResourcePoolClassifierConfig::TDecoder::Database, NMetadata::NInternal::TYDBValue::Utf8(CanonizePath(context.GetExternalData().GetDatabase()))); + result.SetColumn(TResourcePoolClassifierConfig::TDecoder::Name, NMetadata::NInternal::TYDBValue::Utf8(settings.GetObjectId())); + return result; +} + +} // anonymous namespace + +NMetadata::NModifications::TOperationParsingResult TResourcePoolClassifierManager::DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const { + try { + switch (context.GetActivityType()) { + case EActivityType::Create: + case EActivityType::Alter: + return FillResourcePoolClassifierInfo(settings, context); + case EActivityType::Drop: + return FillDropInfo(settings, context); + case EActivityType::Upsert: + return TConclusionStatus::Fail("Upsert operation for RESOURCE_POOL_CLASSIFIER objects is not implemented"); + case EActivityType::Undefined: + return TConclusionStatus::Fail("Undefined operation for RESOURCE_POOL_CLASSIFIER object"); + } + } catch (...) { + return TConclusionStatus::Fail(CurrentExceptionMessage()); + } +} + +NMetadata::NModifications::TOperationParsingResult TResourcePoolClassifierManager::FillResourcePoolClassifierInfo(const NYql::TObjectSettingsImpl& settings, const TInternalModificationContext& context) const { + NMetadata::NInternal::TTableRecord result = GetResourcePoolClassifierRecord(settings, context); + + auto& featuresExtractor = settings.GetFeaturesExtractor(); + featuresExtractor.ValidateResetFeatures(); + + NJson::TJsonValue configJson = NJson::JSON_MAP; + TClassifierSettings resourcePoolClassifierSettings; + for (const auto& [property, setting] : resourcePoolClassifierSettings.GetPropertiesMap()) { + if (std::optional value = featuresExtractor.Extract(property)) { + try { + std::visit(TClassifierSettings::TParser{*value}, setting); + } catch (...) { + throw yexception() << "Failed to parse property " << property << ": " << CurrentExceptionMessage(); + } + } else if (featuresExtractor.ExtractResetFeature(property)) { + if (property == "resource_pool") { + ythrow yexception() << "Cannot reset required property resource_pool"; + } + } else { + continue; + } + + const TString value = std::visit(TClassifierSettings::TExtractor(), setting); + if (property == TResourcePoolClassifierConfig::TDecoder::Rank) { + result.SetColumn(property, NMetadata::NInternal::TYDBValue::Int64(FromString(value))); + } else { + configJson.InsertValue(property, value); + } + } + + if (context.GetActivityType() == EActivityType::Create) { + if (!configJson.GetMap().contains("resource_pool")) { + ythrow yexception() << "Missing required property resource_pool"; + } + + static const TString extraPathSymbolsAllowed = "!\"#$%&'()*+,-.:;<=>?@[\\]^_`{|}~"; + const auto& name = settings.GetObjectId(); + if (const auto brokenAt = PathPartBrokenAt(name, extraPathSymbolsAllowed); brokenAt != name.end()) { + ythrow yexception() << "Symbol '" << *brokenAt << "'" << " is not allowed in the resource pool classifier name '" << name << "'"; + } + } + resourcePoolClassifierSettings.Validate(); + + NJsonWriter::TBuf writer; + writer.WriteJsonValue(&configJson); + result.SetColumn(TResourcePoolClassifierConfig::TDecoder::ConfigJson, NMetadata::NInternal::TYDBValue::Utf8(writer.Str())); + + if (!featuresExtractor.IsFinished()) { + ythrow yexception() << "Unknown property: " << featuresExtractor.GetRemainedParamsString(); + } + + return result; +} + +NMetadata::NModifications::TOperationParsingResult TResourcePoolClassifierManager::FillDropInfo(const NYql::TObjectSettingsImpl& settings, const TInternalModificationContext& context) const { + return GetResourcePoolClassifierRecord(settings, context); +} + +void TResourcePoolClassifierManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const { + auto* actorSystem = context.GetExternalData().GetActorSystem(); + if (!actorSystem) { + controller->OnPreparationProblem("This place needs an actor system. Please contact internal support"); + return; + } + + actorSystem->Register(CreateResourcePoolClassifierPreparationActor(std::move(patchedObjects), std::move(controller), context, alterContext)); +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.h new file mode 100644 index 000000000000..947069f8a1ab --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/manager.h @@ -0,0 +1,21 @@ +#pragma once + +#include "object.h" + +#include + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierManager : public NMetadata::NModifications::TGenericOperationsManager { +protected: + virtual NMetadata::NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; + + virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; + +private: + NMetadata::NModifications::TOperationParsingResult FillResourcePoolClassifierInfo(const NYql::TObjectSettingsImpl& settings, const TInternalModificationContext& context) const; + NMetadata::NModifications::TOperationParsingResult FillDropInfo(const NYql::TObjectSettingsImpl& settings, const TInternalModificationContext& context) const; +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.cpp new file mode 100644 index 000000000000..b34541962d70 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.cpp @@ -0,0 +1,137 @@ +#include "object.h" +#include "behaviour.h" + +#include + + +namespace NKikimr::NKqp { + +namespace { + +using namespace NResourcePool; + + +class TJsonConfigsMerger : public NMetadata::NModifications::IColumnValuesMerger { +public: + virtual TConclusionStatus Merge(Ydb::Value& value, const Ydb::Value& patch) const override { + NJson::TJsonValue selfConfigJson; + if (!NJson::ReadJsonTree(value.text_value(), &selfConfigJson)) { + return TConclusionStatus::Fail("Failed to parse object json config"); + } + + NJson::TJsonValue otherConfigJson; + if (!NJson::ReadJsonTree(patch.text_value(), &otherConfigJson)) { + return TConclusionStatus::Fail("Failed to parse patch json config"); + } + + for (const auto& [key, value] : otherConfigJson.GetMap()) { + selfConfigJson.InsertValue(key, value); + } + + NJsonWriter::TBuf writer; + writer.WriteJsonValue(&selfConfigJson); + *value.mutable_text_value() = writer.Str(); + + return TConclusionStatus::Success(); + } +}; + +} // anonymous namespace + + +//// TResourcePoolClassifierConfig::TDecoder + +TResourcePoolClassifierConfig::TDecoder::TDecoder(const Ydb::ResultSet& rawData) + : DatabaseIdx(GetFieldIndex(rawData, Database)) + , NameIdx(GetFieldIndex(rawData, Name)) + , RankIdx(GetFieldIndex(rawData, Rank)) + , ConfigJsonIdx(GetFieldIndex(rawData, ConfigJson)) +{} + +//// TResourcePoolClassifierConfig + +NMetadata::NModifications::IColumnValuesMerger::TPtr TResourcePoolClassifierConfig::BuildMerger(const TString& columnName) const { + if (columnName == TDecoder::ConfigJson) { + return std::make_shared(); + } + return TBase::BuildMerger(columnName); +} + +bool TResourcePoolClassifierConfig::DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& rawData) { + if (!decoder.Read(decoder.GetDatabaseIdx(), Database, rawData)) { + return false; + } + if (!decoder.Read(decoder.GetNameIdx(), Name, rawData)) { + return false; + } + if (!decoder.Read(decoder.GetRankIdx(), Rank, rawData)) { + Rank = -1; + } + + TString configJsonString; + if (!decoder.Read(decoder.GetConfigJsonIdx(), configJsonString, rawData)) { + return false; + } + if (!NJson::ReadJsonTree(configJsonString, &ConfigJson)) { + return false; + } + + return true; +} + +NMetadata::NInternal::TTableRecord TResourcePoolClassifierConfig::SerializeToRecord() const { + NMetadata::NInternal::TTableRecord result; + result.SetColumn(TDecoder::Database, NMetadata::NInternal::TYDBValue::Utf8(Database)); + result.SetColumn(TDecoder::Name, NMetadata::NInternal::TYDBValue::Utf8(Name)); + result.SetColumn(TDecoder::Rank, NMetadata::NInternal::TYDBValue::Int64(Rank)); + + NJsonWriter::TBuf writer; + writer.WriteJsonValue(&ConfigJson); + result.SetColumn(TDecoder::ConfigJson, NMetadata::NInternal::TYDBValue::Utf8(writer.Str())); + + return result; +} + +TClassifierSettings TResourcePoolClassifierConfig::GetClassifierSettings() const { + TClassifierSettings resourcePoolClassifierSettings; + + resourcePoolClassifierSettings.Rank = Rank; + + const auto& properties = resourcePoolClassifierSettings.GetPropertiesMap(); + for (const auto& [propery, value] : ConfigJson.GetMap()) { + const auto it = properties.find(propery); + if (it == properties.end()) { + continue; + } + try { + std::visit(TClassifierSettings::TParser{value.GetString()}, it->second); + } catch (...) { + continue; + } + } + + return resourcePoolClassifierSettings; +} + +NJson::TJsonValue TResourcePoolClassifierConfig::GetDebugJson() const { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue(TDecoder::Database, Database); + result.InsertValue(TDecoder::Name, Name); + result.InsertValue(TDecoder::Rank, Rank); + result.InsertValue(TDecoder::ConfigJson, ConfigJson); + return result; +} + +bool TResourcePoolClassifierConfig::operator==(const TResourcePoolClassifierConfig& other) const { + return std::tie(Database, Name, Rank, ConfigJson) == std::tie(other.Database, other.Name, other.Rank, other.ConfigJson); +} + +NMetadata::IClassBehaviour::TPtr TResourcePoolClassifierConfig::GetBehaviour() { + return TResourcePoolClassifierBehaviour::GetInstance(); +} + +TString TResourcePoolClassifierConfig::GetTypeId() { + return "RESOURCE_POOL_CLASSIFIER"; +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.h new file mode 100644 index 000000000000..854a15c2827f --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/object.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierConfig : public NMetadata::NModifications::TObject { + using TBase = NMetadata::NModifications::TObject; + + YDB_ACCESSOR_DEF(TString, Database); + YDB_ACCESSOR_DEF(TString, Name); + YDB_ACCESSOR_DEF(i64, Rank); + YDB_ACCESSOR_DEF(NJson::TJsonValue, ConfigJson); + +public: + class TDecoder : public NMetadata::NInternal::TDecoderBase { + private: + YDB_READONLY(i32, DatabaseIdx, -1); + YDB_READONLY(i32, NameIdx, -1); + YDB_READONLY(i32, RankIdx, -1); + YDB_READONLY(i32, ConfigJsonIdx, -1); + + public: + static inline const TString Database = "database"; + static inline const TString Name = "name"; + static inline const TString Rank = "rank"; + static inline const TString ConfigJson = "config"; + + explicit TDecoder(const Ydb::ResultSet& rawData); + }; + + virtual NMetadata::NModifications::IColumnValuesMerger::TPtr BuildMerger(const TString& columnName) const override; + NMetadata::NInternal::TTableRecord SerializeToRecord() const; + bool DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& rawData); + + NResourcePool::TClassifierSettings GetClassifierSettings() const; + NJson::TJsonValue GetDebugJson() const; + + bool operator==(const TResourcePoolClassifierConfig& other) const; + + static NMetadata::IClassBehaviour::TPtr GetBehaviour(); + static TString GetTypeId(); +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.cpp b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.cpp new file mode 100644 index 000000000000..5c08fd5ea280 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.cpp @@ -0,0 +1,37 @@ +#include "snapshot.h" + + +namespace NKikimr::NKqp { + +bool TResourcePoolClassifierSnapshot::DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawData) { + Y_ABORT_UNLESS(rawData.result_sets().size() == 1); + ParseSnapshotObjects(rawData.result_sets()[0], [this](TResourcePoolClassifierConfig&& config) { + ResourcePoolClassifierConfigs[config.GetDatabase()].emplace(config.GetName(), config); + }); + return true; +} + +TString TResourcePoolClassifierSnapshot::DoSerializeToString() const { + NJson::TJsonValue result = NJson::JSON_MAP; + auto& jsonResourcePoolClassifiers = result.InsertValue("resource_pool_classifiers", NJson::JSON_ARRAY); + for (const auto& [_, configsMap] : ResourcePoolClassifierConfigs) { + for (const auto& [_, config] : configsMap) { + jsonResourcePoolClassifiers.AppendValue(config.GetDebugJson()); + } + } + return result.GetStringRobust(); +} + +std::optional TResourcePoolClassifierSnapshot::GetClassifierConfig(const TString& database, const TString& name) const { + const auto databaseIt = ResourcePoolClassifierConfigs.find(database); + if (databaseIt == ResourcePoolClassifierConfigs.end()) { + return std::nullopt; + } + const auto configIt = databaseIt->second.find(name); + if (configIt == databaseIt->second.end()) { + return std::nullopt; + } + return configIt->second; +} + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.h b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.h new file mode 100644 index 000000000000..2ab9130fa295 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/snapshot.h @@ -0,0 +1,26 @@ +#pragma once + +#include "object.h" + +#include + + +namespace NKikimr::NKqp { + +class TResourcePoolClassifierSnapshot : public NMetadata::NFetcher::ISnapshot { + using TBase = NMetadata::NFetcher::ISnapshot; + using TConfigsMap = std::unordered_map>; + + YDB_ACCESSOR_DEF(TConfigsMap, ResourcePoolClassifierConfigs); + +protected: + virtual bool DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawData) override; + virtual TString DoSerializeToString() const override; + +public: + using TBase::TBase; + + std::optional GetClassifierConfig(const TString& database, const TString& name) const; +}; + +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/ya.make b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/ya.make new file mode 100644 index 000000000000..535929964307 --- /dev/null +++ b/ydb/core/kqp/gateway/behaviour/resource_pool_classifier/ya.make @@ -0,0 +1,26 @@ +LIBRARY() + +SRCS( + GLOBAL behaviour.cpp + checker.cpp + fetcher.cpp + initializer.cpp + manager.cpp + object.cpp + snapshot.cpp +) + +PEERDIR( + ydb/core/cms/console + ydb/core/kqp/workload_service/actors + ydb/core/protos + ydb/core/resource_pools + ydb/library/query_actor + ydb/services/metadata/abstract + ydb/services/metadata/initializer + ydb/services/metadata/manager +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/kqp/gateway/behaviour/ya.make b/ydb/core/kqp/gateway/behaviour/ya.make index bd59426cc368..61c62535ac96 100644 --- a/ydb/core/kqp/gateway/behaviour/ya.make +++ b/ydb/core/kqp/gateway/behaviour/ya.make @@ -1,6 +1,7 @@ RECURSE( external_data_source resource_pool + resource_pool_classifier table tablestore ) diff --git a/ydb/core/kqp/gateway/ya.make b/ydb/core/kqp/gateway/ya.make index 51767a6992a1..c4c6fe631a42 100644 --- a/ydb/core/kqp/gateway/ya.make +++ b/ydb/core/kqp/gateway/ya.make @@ -18,6 +18,7 @@ PEERDIR( ydb/core/kqp/gateway/behaviour/table ydb/core/kqp/gateway/behaviour/external_data_source ydb/core/kqp/gateway/behaviour/resource_pool + ydb/core/kqp/gateway/behaviour/resource_pool_classifier ydb/core/kqp/gateway/behaviour/view ydb/core/kqp/gateway/utils ydb/library/yql/providers/result/expr_nodes diff --git a/ydb/core/kqp/host/kqp_host.cpp b/ydb/core/kqp/host/kqp_host.cpp index 3e98f7db8aa3..12e4ec78832b 100644 --- a/ydb/core/kqp/host/kqp_host.cpp +++ b/ydb/core/kqp/host/kqp_host.cpp @@ -1033,7 +1033,8 @@ class TKqpHost : public IKqpHost { std::optional federatedQuerySetup, const TIntrusiveConstPtr& userToken, const NKikimr::NMiniKQL::IFunctionRegistry* funcRegistry, bool keepConfigChanges, bool isInternalCall, TKqpTempTablesState::TConstPtr tempTablesState = nullptr, NActors::TActorSystem* actorSystem = nullptr, - NYql::TExprContext* ctx = nullptr, const NKikimrConfig::TQueryServiceConfig& queryServiceConfig = NKikimrConfig::TQueryServiceConfig()) + NYql::TExprContext* ctx = nullptr, const NKikimrConfig::TQueryServiceConfig& queryServiceConfig = NKikimrConfig::TQueryServiceConfig(), + const TIntrusivePtr& userRequestContext = nullptr) : Gateway(gateway) , Cluster(cluster) , GUCSettings(gUCSettings) @@ -1044,7 +1045,7 @@ class TKqpHost : public IKqpHost { , KeepConfigChanges(keepConfigChanges) , IsInternalCall(isInternalCall) , FederatedQuerySetup(federatedQuerySetup) - , SessionCtx(new TKikimrSessionContext(funcRegistry, config, TAppData::TimeProvider, TAppData::RandomProvider, userToken)) + , SessionCtx(new TKikimrSessionContext(funcRegistry, config, TAppData::TimeProvider, TAppData::RandomProvider, userToken, nullptr, userRequestContext)) , Config(config) , TypesCtx(MakeIntrusive()) , PlanBuilder(CreatePlanBuilder(*TypesCtx)) @@ -1958,10 +1959,10 @@ TIntrusivePtr CreateKqpHost(TIntrusivePtr gateway, const const TString& database, TKikimrConfiguration::TPtr config, IModuleResolver::TPtr moduleResolver, std::optional federatedQuerySetup, const TIntrusiveConstPtr& userToken, const TGUCSettings::TPtr& gUCSettings, const NKikimrConfig::TQueryServiceConfig& queryServiceConfig, const TMaybe& applicationName, const NKikimr::NMiniKQL::IFunctionRegistry* funcRegistry, bool keepConfigChanges, - bool isInternalCall, TKqpTempTablesState::TConstPtr tempTablesState, NActors::TActorSystem* actorSystem, NYql::TExprContext* ctx) + bool isInternalCall, TKqpTempTablesState::TConstPtr tempTablesState, NActors::TActorSystem* actorSystem, NYql::TExprContext* ctx, const TIntrusivePtr& userRequestContext) { return MakeIntrusive(gateway, cluster, database, gUCSettings, applicationName, config, moduleResolver, federatedQuerySetup, userToken, funcRegistry, - keepConfigChanges, isInternalCall, std::move(tempTablesState), actorSystem, ctx, queryServiceConfig); + keepConfigChanges, isInternalCall, std::move(tempTablesState), actorSystem, ctx, queryServiceConfig, userRequestContext); } } // namespace NKqp diff --git a/ydb/core/kqp/host/kqp_host.h b/ydb/core/kqp/host/kqp_host.h index 85a7025a9e1b..5af2b9a03afc 100644 --- a/ydb/core/kqp/host/kqp_host.h +++ b/ydb/core/kqp/host/kqp_host.h @@ -123,7 +123,7 @@ TIntrusivePtr CreateKqpHost(TIntrusivePtr gateway, const NKikimrConfig::TQueryServiceConfig& queryServiceConfig, const TMaybe& applicationName = Nothing(), const NKikimr::NMiniKQL::IFunctionRegistry* funcRegistry = nullptr, bool keepConfigChanges = false, bool isInternalCall = false, TKqpTempTablesState::TConstPtr tempTablesState = nullptr, NActors::TActorSystem* actorSystem = nullptr /*take from TLS by default*/, - NYql::TExprContext* ctx = nullptr); + NYql::TExprContext* ctx = nullptr, const TIntrusivePtr& userRequestContext = nullptr); } // namespace NKqp } // namespace NKikimr diff --git a/ydb/core/kqp/host/kqp_runner.cpp b/ydb/core/kqp/host/kqp_runner.cpp index 3f42256e7790..82f5042fb637 100644 --- a/ydb/core/kqp/host/kqp_runner.cpp +++ b/ydb/core/kqp/host/kqp_runner.cpp @@ -146,7 +146,7 @@ class TKqpRunner : public IKqpRunner { , Config(sessionCtx->ConfigPtr()) , TransformCtx(transformCtx) , OptimizeCtx(MakeIntrusive(cluster, Config, sessionCtx->QueryPtr(), - sessionCtx->TablesPtr())) + sessionCtx->TablesPtr(), sessionCtx->GetUserRequestContext())) , BuildQueryCtx(MakeIntrusive()) , Pctx(TKqpProviderContext(*OptimizeCtx, Config->CostBasedOptimizationLevel.Get().GetOrElse(TDqSettings::TDefault::CostBasedOptimizationLevel))) { diff --git a/ydb/core/kqp/opt/kqp_opt.h b/ydb/core/kqp/opt/kqp_opt.h index 7e8181a85a8f..799805ca32b8 100644 --- a/ydb/core/kqp/opt/kqp_opt.h +++ b/ydb/core/kqp/opt/kqp_opt.h @@ -10,11 +10,13 @@ namespace NKikimr::NKqp::NOpt { struct TKqpOptimizeContext : public TSimpleRefCount { TKqpOptimizeContext(const TString& cluster, const NYql::TKikimrConfiguration::TPtr& config, - const TIntrusivePtr queryCtx, const TIntrusivePtr& tables) + const TIntrusivePtr queryCtx, const TIntrusivePtr& tables, + const TIntrusivePtr& userRequestContext) : Cluster(cluster) , Config(config) , QueryCtx(queryCtx) , Tables(tables) + , UserRequestContext(userRequestContext) { YQL_ENSURE(QueryCtx); YQL_ENSURE(Tables); @@ -24,6 +26,7 @@ struct TKqpOptimizeContext : public TSimpleRefCount { const NYql::TKikimrConfiguration::TPtr Config; const TIntrusivePtr QueryCtx; const TIntrusivePtr Tables; + const TIntrusivePtr UserRequestContext; int JoinsCount{}; int EquiJoinsCount{}; diff --git a/ydb/core/kqp/opt/kqp_query_plan.cpp b/ydb/core/kqp/opt/kqp_query_plan.cpp index bf0e642eb5d9..38f011a3fa2f 100644 --- a/ydb/core/kqp/opt/kqp_query_plan.cpp +++ b/ydb/core/kqp/opt/kqp_query_plan.cpp @@ -2363,11 +2363,21 @@ void PhyQuerySetTxPlans(NKqpProto::TKqpPhyQuery& queryProto, const TKqpPhysicalQ txPlans.emplace_back(phyTx.GetPlan()); } + TString queryStats = ""; + if (optCtx && optCtx->UserRequestContext && optCtx->UserRequestContext->PoolId) { + NJsonWriter::TBuf writer; + writer.BeginObject(); + writer.WriteKey("ResourcePoolId").WriteString(optCtx->UserRequestContext->PoolId); + writer.EndObject(); + + queryStats = writer.Str(); + } + NJsonWriter::TBuf writer; writer.SetIndentSpaces(2); WriteCommonTablesInfo(writer, serializerCtx.Tables); - queryProto.SetQueryPlan(SerializeTxPlans(txPlans, optCtx, writer.Str())); + queryProto.SetQueryPlan(SerializeTxPlans(txPlans, optCtx, writer.Str(), queryStats)); } void FillAggrStat(NJson::TJsonValue& node, const NYql::NDqProto::TDqStatsAggr& aggr, const TString& name) { @@ -2707,7 +2717,7 @@ TString AddExecStatsToTxPlan(const TString& txPlanJson, const NYql::NDqProto::TD return AddExecStatsToTxPlan(txPlanJson, stats, TIntrusivePtr()); } -TString SerializeAnalyzePlan(const NKqpProto::TKqpStatsQuery& queryStats) { +TString SerializeAnalyzePlan(const NKqpProto::TKqpStatsQuery& queryStats, const TString& poolId) { TVector txPlans; for (const auto& execStats: queryStats.GetExecutions()) { for (const auto& txPlan: execStats.GetTxPlansWithStats()) { @@ -2731,7 +2741,10 @@ TString SerializeAnalyzePlan(const NKqpProto::TKqpStatsQuery& queryStats) { writer.WriteKey("ProcessCpuTimeUs").WriteLongLong(queryStats.GetWorkerCpuTimeUs()); writer.WriteKey("TotalDurationUs").WriteLongLong(queryStats.GetDurationUs()); - writer.WriteKey("QueuedTimeUs").WriteLongLong(queryStats.GetQueuedTimeUs()); + if (poolId) { + writer.WriteKey("QueuedTimeUs").WriteLongLong(queryStats.GetQueuedTimeUs()); + writer.WriteKey("ResourcePoolId").WriteString(poolId); + } writer.EndObject(); return SerializeTxPlans(txPlans, TIntrusivePtr(), "", writer.Str()); diff --git a/ydb/core/kqp/opt/kqp_query_plan.h b/ydb/core/kqp/opt/kqp_query_plan.h index 8adbf2b20866..7f720dc4c6ca 100644 --- a/ydb/core/kqp/opt/kqp_query_plan.h +++ b/ydb/core/kqp/opt/kqp_query_plan.h @@ -44,7 +44,7 @@ void PhyQuerySetTxPlans(NKqpProto::TKqpPhyQuery& queryProto, const NYql::NNodes: */ TString AddExecStatsToTxPlan(const TString& txPlan, const NYql::NDqProto::TDqExecutionStats& stats); -TString SerializeAnalyzePlan(const NKqpProto::TKqpStatsQuery& queryStats); +TString SerializeAnalyzePlan(const NKqpProto::TKqpStatsQuery& queryStats, const TString& poolId = ""); TString SerializeScriptPlan(const TVector& queryPlans); diff --git a/ydb/core/kqp/provider/yql_kikimr_gateway_ut.cpp b/ydb/core/kqp/provider/yql_kikimr_gateway_ut.cpp index b16d43a67527..b07f378a5280 100644 --- a/ydb/core/kqp/provider/yql_kikimr_gateway_ut.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_gateway_ut.cpp @@ -232,7 +232,7 @@ void TestCreateResourcePool(TTestActorRuntime& runtime, TIntrusivePtr gateway, const TString& poolId) { TDropObjectSettings settings("RESOURCE_POOL", poolId, {}); - TestDropObjectCommon(runtime, gateway, settings, TStringBuilder() << "/Root/.resource_pools/" << poolId); + TestDropObjectCommon(runtime, gateway, settings, TStringBuilder() << "/Root/.metadata/workload_manager/pools/" << poolId); } TKikimrRunner GetKikimrRunnerWithResourcePools() { diff --git a/ydb/core/kqp/provider/yql_kikimr_provider.h b/ydb/core/kqp/provider/yql_kikimr_provider.h index cdca12b6a156..7dccfe0225a2 100644 --- a/ydb/core/kqp/provider/yql_kikimr_provider.h +++ b/ydb/core/kqp/provider/yql_kikimr_provider.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -460,12 +461,14 @@ class TKikimrSessionContext : public TThrRefBase { TIntrusivePtr timeProvider, TIntrusivePtr randomProvider, const TIntrusiveConstPtr& userToken, - TIntrusivePtr txCtx = nullptr) + TIntrusivePtr txCtx = nullptr, + const TIntrusivePtr& userRequestContext = nullptr) : Configuration(config) , TablesData(MakeIntrusive()) , QueryCtx(MakeIntrusive(functionRegistry, timeProvider, randomProvider)) , TxCtx(txCtx) , UserToken(userToken) + , UserRequestContext(userRequestContext) {} TKikimrSessionContext(const TKikimrSessionContext&) = delete; @@ -547,6 +550,10 @@ class TKikimrSessionContext : public TThrRefBase { return UserToken; } + const TIntrusivePtr& GetUserRequestContext() const { + return UserRequestContext; + } + private: TString UserName; TString Cluster; @@ -558,6 +565,7 @@ class TKikimrSessionContext : public TThrRefBase { TIntrusivePtr TxCtx; NKikimr::NKqp::TKqpTempTablesState::TConstPtr TempTablesState; TIntrusiveConstPtr UserToken; + TIntrusivePtr UserRequestContext; }; TIntrusivePtr CreateKikimrDataSource( diff --git a/ydb/core/kqp/proxy_service/kqp_proxy_service.cpp b/ydb/core/kqp/proxy_service/kqp_proxy_service.cpp index deb34ffb92bc..3c40f3f0b855 100644 --- a/ydb/core/kqp/proxy_service/kqp_proxy_service.cpp +++ b/ydb/core/kqp/proxy_service/kqp_proxy_service.cpp @@ -233,6 +233,7 @@ class TKqpProxyService : public TActorBootstrapped { IEventHandle::FlagTrackDelivery); WhiteBoardService = NNodeWhiteboard::MakeNodeWhiteboardServiceId(SelfId().NodeId()); + ResourcePoolsCache.UpdateFeatureFlags(FeatureFlags, ActorContext()); if (auto& cfg = TableServiceConfig.GetSpillingServiceConfig().GetLocalFileConfig(); cfg.GetEnable()) { SpillingService = TlsActivationContext->ExecutorThread.RegisterActor(NYql::NDq::CreateDqLocalFileSpillingService( @@ -475,6 +476,8 @@ class TKqpProxyService : public TActorBootstrapped { Send(TActivationContext::InterconnectProxy(node), new TEvents::TEvUnsubscribe); }); + ResourcePoolsCache.UnsubscribeFromResourcePoolClassifiers(ActorContext()); + return TActor::PassAway(); } @@ -492,6 +495,7 @@ class TKqpProxyService : public TActorBootstrapped { UpdateYqlLogLevels(); FeatureFlags.Swap(event.MutableConfig()->MutableFeatureFlags()); + ResourcePoolsCache.UpdateFeatureFlags(FeatureFlags, ActorContext()); auto responseEv = MakeHolder(event); Send(ev->Sender, responseEv.Release(), IEventHandle::FlagTrackDelivery, ev->Cookie); @@ -1347,6 +1351,8 @@ class TKqpProxyService : public TActorBootstrapped { hFunc(TEvKqp::TEvListSessionsRequest, Handle); hFunc(TEvKqp::TEvListProxyNodesRequest, Handle); hFunc(NWorkload::TEvUpdatePoolInfo, Handle); + hFunc(NWorkload::TEvUpdateDatabaseInfo, Handle); + hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); default: Y_ABORT("TKqpProxyService: unexpected event type: %" PRIx32 " event: %s", ev->GetTypeRewrite(), ev->ToString().data()); @@ -1570,23 +1576,26 @@ class TKqpProxyService : public TActorBootstrapped { } bool TryFillPoolInfoFromCache(TEvKqp::TEvQueryRequest::TPtr& ev, ui64 requestId) { - if (!FeatureFlags.GetEnableResourcePools()) { + ResourcePoolsCache.UpdateFeatureFlags(FeatureFlags, ActorContext()); + + const auto& database = ev->Get()->GetDatabase(); + if (!ResourcePoolsCache.ResourcePoolsEnabled(database)) { ev->Get()->SetPoolId(""); return true; } + const auto& userToken = ev->Get()->GetUserToken(); if (!ev->Get()->GetPoolId()) { - ev->Get()->SetPoolId(NResourcePool::DEFAULT_POOL_ID); + ev->Get()->SetPoolId(ResourcePoolsCache.GetPoolId(database, userToken, ActorContext())); } const auto& poolId = ev->Get()->GetPoolId(); - const auto& poolInfo = ResourcePoolsCache.GetPoolInfo(ev->Get()->GetDatabase(), poolId); + const auto& poolInfo = ResourcePoolsCache.GetPoolInfo(database, poolId, ActorContext()); if (!poolInfo) { return true; } const auto& securityObject = poolInfo->SecurityObject; - const auto& userToken = ev->Get()->GetUserToken(); if (securityObject && userToken && !userToken->GetSerializedToken().empty()) { if (!securityObject->CheckAccess(NACLib::EAccessRights::DescribeSchema, *userToken)) { ReplyProcessError(Ydb::StatusIds::NOT_FOUND, TStringBuilder() << "Resource pool " << poolId << " not found or you don't have access permissions", requestId); @@ -1801,7 +1810,15 @@ class TKqpProxyService : public TActorBootstrapped { } void Handle(NWorkload::TEvUpdatePoolInfo::TPtr& ev) { - ResourcePoolsCache.UpdatePoolInfo(ev->Get()->Database, ev->Get()->PoolId, ev->Get()->Config, ev->Get()->SecurityObject); + ResourcePoolsCache.UpdatePoolInfo(ev->Get()->Database, ev->Get()->PoolId, ev->Get()->Config, ev->Get()->SecurityObject, ActorContext()); + } + + void Handle(NWorkload::TEvUpdateDatabaseInfo::TPtr& ev) { + ResourcePoolsCache.UpdateDatabaseInfo(ev->Get()->Database, ev->Get()->Serverless); + } + + void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { + ResourcePoolsCache.UpdateResourcePoolClassifiersInfo(ev->Get()->GetSnapshotAs(), ActorContext()); } private: diff --git a/ydb/core/kqp/proxy_service/kqp_proxy_service_impl.h b/ydb/core/kqp/proxy_service/kqp_proxy_service_impl.h index 61620e2f2768..e0d91fb8845b 100644 --- a/ydb/core/kqp/proxy_service/kqp_proxy_service_impl.h +++ b/ydb/core/kqp/proxy_service/kqp_proxy_service_impl.h @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include @@ -417,39 +419,227 @@ class TLocalSessionsRegistry { }; class TResourcePoolsCache { + struct TClassifierInfo { + const TString Membername; + const TString PoolId; + const i64 Rank; + + TClassifierInfo(const NResourcePool::TClassifierSettings& classifierSettings) + : Membername(classifierSettings.Membername) + , PoolId(classifierSettings.ResourcePool) + , Rank(classifierSettings.Rank) + {} + }; + + struct TDatabaseInfo { + std::unordered_map ResourcePoolsClassifiers = {}; + std::map RankToClassifierInfo = {}; + std::unordered_map> UserToResourcePool = {}; + bool Serverless = false; + }; + struct TPoolInfo { NResourcePool::TPoolSettings Config; std::optional SecurityObject; + bool Expired = false; }; public: - std::optional GetPoolInfo(const TString& database, const TString& poolId) const { + bool ResourcePoolsEnabled(const TString& database) const { + if (!EnableResourcePools) { + return false; + } + + if (EnableResourcePoolsOnServerless) { + return true; + } + + const auto databaseInfo = GetDatabaseInfo(database); + return !databaseInfo || !databaseInfo->Serverless; + } + + TString GetPoolId(const TString& database, const TIntrusiveConstPtr& userToken, TActorContext actorContext) { + if (!userToken || userToken->GetUserSID().empty()) { + return NResourcePool::DEFAULT_POOL_ID; + } + + TDatabaseInfo& databaseInfo = *GetOrCreateDatabaseInfo(database); + auto [resultPoolId, resultRank] = GetPoolIdFromClassifiers(database, userToken->GetUserSID(), databaseInfo, userToken, actorContext); + for (const auto& userSID : userToken->GetGroupSIDs()) { + const auto& [poolId, rank] = GetPoolIdFromClassifiers(database, userSID, databaseInfo, userToken, actorContext); + if (poolId && (!resultPoolId || resultRank > rank)) { + resultPoolId = poolId; + resultRank = rank; + } + } + + return resultPoolId ? resultPoolId : NResourcePool::DEFAULT_POOL_ID; + } + + std::optional GetPoolInfo(const TString& database, const TString& poolId, TActorContext actorContext) const { auto it = PoolsCache.find(GetPoolKey(database, poolId)); if (it == PoolsCache.end()) { + actorContext.Send(MakeKqpWorkloadServiceId(actorContext.SelfID.NodeId()), new NWorkload::TEvSubscribeOnPoolChanges(database, poolId)); return std::nullopt; } return it->second; } - void UpdatePoolInfo(const TString& database, const TString& poolId, const std::optional& config, const std::optional& securityObject) { + void UpdateFeatureFlags(const NKikimrConfig::TFeatureFlags& featureFlags, TActorContext actorContext) { + EnableResourcePools = featureFlags.GetEnableResourcePools(); + EnableResourcePoolsOnServerless = featureFlags.GetEnableResourcePoolsOnServerless(); + UpdateResourcePoolClassifiersSubscription(actorContext); + } + + void UpdateDatabaseInfo(const TString& database, bool serverless) { + GetOrCreateDatabaseInfo(database)->Serverless = serverless; + } + + void UpdatePoolInfo(const TString& database, const TString& poolId, const std::optional& config, const std::optional& securityObject, TActorContext actorContext) { + bool clearClassifierCache = false; + const TString& poolKey = GetPoolKey(database, poolId); if (!config) { - PoolsCache.erase(poolKey); - return; + auto it = PoolsCache.find(poolKey); + if (it == PoolsCache.end()) { + return; + } + if (it->second.Expired) { + // Pool was dropped + clearClassifierCache = true; + PoolsCache.erase(it); + } else { + // Refresh pool subscription + it->second.Expired = true; + actorContext.Send(MakeKqpWorkloadServiceId(actorContext.SelfID.NodeId()), new NWorkload::TEvSubscribeOnPoolChanges(database, poolId)); + } + } else { + auto& poolInfo = PoolsCache[poolKey]; + clearClassifierCache = poolInfo.SecurityObject != securityObject; + poolInfo.Config = *config; + poolInfo.SecurityObject = securityObject; + poolInfo.Expired = false; + } + + if (clearClassifierCache) { + GetOrCreateDatabaseInfo(database)->UserToResourcePool.clear(); + } + } + + void UpdateResourcePoolClassifiersInfo(const TResourcePoolClassifierSnapshot* snapsot, TActorContext actorContext) { + auto resourcePoolClassifierConfigs = snapsot->GetResourcePoolClassifierConfigs(); + for (auto& [database, databaseInfo] : DatabasesCache) { + auto it = resourcePoolClassifierConfigs.find(database); + if (it != resourcePoolClassifierConfigs.end()) { + UpdateDatabaseResourcePoolClassifiers(database, databaseInfo, std::move(it->second), actorContext); + resourcePoolClassifierConfigs.erase(it); + } else if (!databaseInfo.ResourcePoolsClassifiers.empty()) { + databaseInfo.ResourcePoolsClassifiers.clear(); + databaseInfo.RankToClassifierInfo.clear(); + databaseInfo.UserToResourcePool.clear(); + } + } + for (auto& [database, configsMap] : resourcePoolClassifierConfigs) { + UpdateDatabaseResourcePoolClassifiers(database, *GetOrCreateDatabaseInfo(database), std::move(configsMap), actorContext); } + } - auto& poolInfo = PoolsCache[poolKey]; - poolInfo.Config = *config; - poolInfo.SecurityObject = securityObject; + void UnsubscribeFromResourcePoolClassifiers(TActorContext actorContext) { + if (SubscribedOnResourcePoolClassifiers) { + SubscribedOnResourcePoolClassifiers = false; + actorContext.Send(NMetadata::NProvider::MakeServiceId(actorContext.SelfID.NodeId()), new NMetadata::NProvider::TEvUnsubscribeExternal(std::make_shared())); + } } private: + void UpdateResourcePoolClassifiersSubscription(TActorContext actorContext) { + if (EnableResourcePools) { + SubscribeOnResourcePoolClassifiers(actorContext); + } else { + UnsubscribeFromResourcePoolClassifiers(actorContext); + } + } + + void SubscribeOnResourcePoolClassifiers(TActorContext actorContext) { + if (!SubscribedOnResourcePoolClassifiers && NMetadata::NProvider::TServiceOperator::IsEnabled()) { + SubscribedOnResourcePoolClassifiers = true; + actorContext.Send(NMetadata::NProvider::MakeServiceId(actorContext.SelfID.NodeId()), new NMetadata::NProvider::TEvSubscribeExternal(std::make_shared())); + } + } + + void UpdateDatabaseResourcePoolClassifiers(const TString& database, TDatabaseInfo& databaseInfo, std::unordered_map&& configsMap, TActorContext actorContext) { + if (databaseInfo.ResourcePoolsClassifiers == configsMap) { + return; + } + + databaseInfo.ResourcePoolsClassifiers.swap(configsMap); + databaseInfo.UserToResourcePool.clear(); + databaseInfo.RankToClassifierInfo.clear(); + for (const auto& [_, classifier] : databaseInfo.ResourcePoolsClassifiers) { + const auto& classifierSettings = classifier.GetClassifierSettings(); + databaseInfo.RankToClassifierInfo.insert({classifier.GetRank(), TClassifierInfo(classifierSettings)}); + if (!PoolsCache.contains(classifierSettings.ResourcePool)) { + actorContext.Send(MakeKqpWorkloadServiceId(actorContext.SelfID.NodeId()), new NWorkload::TEvSubscribeOnPoolChanges(database, classifierSettings.ResourcePool)); + } + } + } + + std::pair GetPoolIdFromClassifiers(const TString& database, const TString& userSID, TDatabaseInfo& databaseInfo, const TIntrusiveConstPtr& userToken, TActorContext actorContext) const { + auto& usersMap = databaseInfo.UserToResourcePool; + if (const auto it = usersMap.find(userSID); it != usersMap.end()) { + return it->second; + } + + TString poolId = ""; + i64 rank = -1; + for (const auto& [_, classifier] : databaseInfo.RankToClassifierInfo) { + if (classifier.Membername != userSID) { + continue; + } + + auto it = PoolsCache.find(GetPoolKey(database, classifier.PoolId)); + if (it == PoolsCache.end()) { + actorContext.Send(MakeKqpWorkloadServiceId(actorContext.SelfID.NodeId()), new NWorkload::TEvSubscribeOnPoolChanges(database, classifier.PoolId)); + continue; + } + + if (userToken && !userToken->GetSerializedToken().empty() && !it->second.SecurityObject->CheckAccess(NACLib::DescribeSchema | NACLib::SelectRow, *userToken)) { + continue; + } + + poolId = classifier.PoolId; + rank = classifier.Rank; + break; + } + + usersMap[userSID] = {poolId, rank}; + return {poolId, rank}; + } + + TDatabaseInfo* GetOrCreateDatabaseInfo(const TString& database) { + const TString& path = CanonizePath(database); + if (const auto it = DatabasesCache.find(path); it != DatabasesCache.end()) { + return &it->second; + } + return &DatabasesCache.insert({path, TDatabaseInfo{}}).first->second; + } + + const TDatabaseInfo* GetDatabaseInfo(const TString& database) const { + const auto it = DatabasesCache.find(CanonizePath(database)); + return it != DatabasesCache.end() ? &it->second : nullptr; + } + static TString GetPoolKey(const TString& database, const TString& poolId) { return CanonizePath(TStringBuilder() << database << "/" << poolId); } private: std::unordered_map PoolsCache; + std::unordered_map DatabasesCache; + + bool EnableResourcePools = false; + bool EnableResourcePoolsOnServerless = false; + bool SubscribedOnResourcePoolClassifiers = false; }; } // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/proxy_service/ya.make b/ydb/core/kqp/proxy_service/ya.make index a69e5c7ca7b2..e1c2b9e1b76a 100644 --- a/ydb/core/kqp/proxy_service/ya.make +++ b/ydb/core/kqp/proxy_service/ya.make @@ -17,6 +17,7 @@ PEERDIR( ydb/core/kqp/common ydb/core/kqp/common/events ydb/core/kqp/counters + ydb/core/kqp/gateway/behaviour/resource_pool_classifier ydb/core/kqp/proxy_service/proto ydb/core/kqp/run_script_actor ydb/core/kqp/workload_service diff --git a/ydb/core/kqp/session_actor/kqp_session_actor.cpp b/ydb/core/kqp/session_actor/kqp_session_actor.cpp index e3b18878a325..2fc6e0e2d2ef 100644 --- a/ydb/core/kqp/session_actor/kqp_session_actor.cpp +++ b/ydb/core/kqp/session_actor/kqp_session_actor.cpp @@ -1359,7 +1359,7 @@ class TKqpSessionActor : public TActorBootstrapped { executionStats.Swap(&stats); stats = QueryState->QueryStats.ToProto(); stats.MutableExecutions()->MergeFrom(executionStats.GetExecutions()); - ev->Get()->Record.SetQueryPlan(SerializeAnalyzePlan(stats)); + ev->Get()->Record.SetQueryPlan(SerializeAnalyzePlan(stats, QueryState->UserRequestContext->PoolId)); } } @@ -1607,7 +1607,7 @@ class TKqpSessionActor : public TActorBootstrapped { if (QueryState->ReportStats()) { auto stats = QueryState->QueryStats.ToProto(); if (QueryState->GetStatsMode() >= Ydb::Table::QueryStatsCollection::STATS_COLLECTION_FULL) { - response->SetQueryPlan(SerializeAnalyzePlan(stats)); + response->SetQueryPlan(SerializeAnalyzePlan(stats, QueryState->UserRequestContext->PoolId)); response->SetQueryAst(QueryState->CompileResult->PreparedQuery->GetPhysicalQuery().GetQueryAst()); } response->MutableQueryStats()->Swap(&stats); diff --git a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp index 5471bef3c1be..e7d7f1eb8aca 100644 --- a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp +++ b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6215,8 +6216,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { // ALTER RESOURCE POOL checkDisabled(R"( ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30), - SET QUEUE_SIZE 100, + SET (CONCURRENT_QUERY_LIMIT = 30, QUEUE_SIZE = 100), RESET (QUERY_MEMORY_LIMIT_PERCENT_PER_NODE); )"); @@ -6226,6 +6226,57 @@ Y_UNIT_TEST_SUITE(KqpScheme) { "Path does not exist"); } + Y_UNIT_TEST(DisableResourcePoolsOnServerless) { + auto ydb = NWorkload::TYdbSetupSettings() + .CreateSampleTenants(true) + .EnableResourcePoolsOnServerless(false) + .Create(); + + auto checkDisabled = [](const auto& result) { + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Resource pools are disabled for serverless domains. Please contact your system administrator to enable it"); + }; + + auto checkNotFound = [](const auto& result) { + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Path does not exist"); + }; + + const auto& createSql = R"( + CREATE RESOURCE POOL MyResourcePool WITH ( + CONCURRENT_QUERY_LIMIT=20, + QUEUE_SIZE=1000 + );)"; + + const auto& alterSql = R"( + ALTER RESOURCE POOL MyResourcePool + SET (CONCURRENT_QUERY_LIMIT = 30, QUEUE_SIZE = 100), + RESET (QUERY_MEMORY_LIMIT_PERCENT_PER_NODE); + )"; + + const auto& dropSql = "DROP RESOURCE POOL MyResourcePool;"; + + auto settings = NWorkload::TQueryRunnerSettings().PoolId(""); + + // Dedicated, enabled + settings.Database(ydb->GetSettings().GetDedicatedTenantName()).NodeIndex(1); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(createSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(alterSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(dropSql, settings)); + + // Shared, enabled + settings.Database(ydb->GetSettings().GetSharedTenantName()).NodeIndex(2); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(createSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(alterSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(dropSql, settings)); + + // Serverless, disabled + settings.Database(ydb->GetSettings().GetServerlessTenantName()).NodeIndex(2); + checkDisabled(ydb->ExecuteQuery(createSql, settings)); + checkNotFound(ydb->ExecuteQuery(alterSql, settings)); + checkNotFound(ydb->ExecuteQuery(dropSql, settings)); + } + Y_UNIT_TEST(ResourcePoolsValidation) { NKikimrConfig::TAppConfig config; config.MutableFeatureFlags()->SetEnableResourcePools(true); @@ -6253,7 +6304,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { result = session.ExecuteSchemeQuery(R"( ALTER RESOURCE POOL MyResourcePool - SET ANOTHER_LIMIT 5, + SET (ANOTHER_LIMIT = 5), RESET (SOME_LIMIT); )").GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); @@ -6265,6 +6316,20 @@ Y_UNIT_TEST_SUITE(KqpScheme) { );)").GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Failed to parse property concurrent_query_limit:"); + + result = session.ExecuteSchemeQuery(TStringBuilder() << R"( + CREATE RESOURCE POOL MyResourcePool WITH ( + CONCURRENT_QUERY_LIMIT=)" << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT + 1 << R"( + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::SCHEME_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Invalid resource pool configuration, concurrent_query_limit is " << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT + 1 << ", that exceeds limit in " << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT); + + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL MyResourcePool WITH ( + QUEUE_SIZE=1 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::SCHEME_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Invalid resource pool configuration, queue_size unsupported without concurrent_query_limit or database_load_cpu_threshold"); } Y_UNIT_TEST(CreateResourcePool) { @@ -6288,7 +6353,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); auto& runtime = *kikimr.GetTestServer().GetRuntime(); - auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.resource_pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); + auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.metadata/workload_manager/pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); const auto& resourcePool = resourcePoolDesc->ResultSet.at(0); UNIT_ASSERT_VALUES_EQUAL(resourcePool.Kind, NSchemeCache::TSchemeCacheNavigate::EKind::KindResourcePool); UNIT_ASSERT(resourcePool.ResourcePoolInfo); @@ -6320,7 +6385,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); auto& runtime = *kikimr.GetTestServer().GetRuntime(); - auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.resource_pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); + auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.metadata/workload_manager/pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); UNIT_ASSERT_VALUES_EQUAL(resourcePoolDesc->ResultSet.at(0).Kind, NSchemeCache::TSchemeCacheNavigate::EKind::KindResourcePool); } @@ -6331,7 +6396,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { );)"; auto result = session.ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); - UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Check failed: path: '/Root/.resource_pools/MyResourcePool', error: path exist"); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Check failed: path: '/Root/.metadata/workload_manager/pools/MyResourcePool', error: path exist"); } } @@ -6356,7 +6421,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); auto& runtime = *kikimr.GetTestServer().GetRuntime(); - auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.resource_pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); + auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.metadata/workload_manager/pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); const auto& properties = resourcePoolDesc->ResultSet.at(0).ResourcePoolInfo->Description.GetProperties().GetProperties(); UNIT_ASSERT_VALUES_EQUAL(properties.size(), 2); UNIT_ASSERT_VALUES_EQUAL(properties.at("concurrent_query_limit"), "20"); @@ -6366,15 +6431,14 @@ Y_UNIT_TEST_SUITE(KqpScheme) { { auto query = R"( ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30), - SET QUEUE_SIZE 100, + SET (CONCURRENT_QUERY_LIMIT = 30, QUEUE_SIZE = 100), RESET (QUERY_MEMORY_LIMIT_PERCENT_PER_NODE); )"; auto result = session.ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); auto& runtime = *kikimr.GetTestServer().GetRuntime(); - auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.resource_pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); + auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.metadata/workload_manager/pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); const auto& properties = resourcePoolDesc->ResultSet.at(0).ResourcePoolInfo->Description.GetProperties().GetProperties(); UNIT_ASSERT_VALUES_EQUAL(properties.size(), 3); UNIT_ASSERT_VALUES_EQUAL(properties.at("concurrent_query_limit"), "30"); @@ -6396,8 +6460,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto query = R"( ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30), - SET QUEUE_SIZE 100, + SET (CONCURRENT_QUERY_LIMIT = 30, QUEUE_SIZE = 100), RESET (QUERY_MEMORY_LIMIT_PERCENT_PER_NODE); )"; auto result = session.ExecuteSchemeQuery(query).GetValueSync(); @@ -6431,7 +6494,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { } auto& runtime = *kikimr.GetTestServer().GetRuntime(); - auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.resource_pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); + auto resourcePoolDesc = Navigate(runtime, runtime.AllocateEdgeActor(), "Root/.metadata/workload_manager/pools/MyResourcePool", NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); const auto& resourcePool = resourcePoolDesc->ResultSet.at(0); UNIT_ASSERT_VALUES_EQUAL(resourcePoolDesc->ErrorCount, 1); UNIT_ASSERT_VALUES_EQUAL(resourcePool.Kind, NSchemeCache::TSchemeCacheNavigate::EKind::KindUnknown); @@ -6452,6 +6515,430 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto result = session.ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SCHEME_ERROR, result.GetIssues().ToString()); } + + Y_UNIT_TEST(DisableResourcePoolClassifiers) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(false); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(false)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + auto checkQuery = [&session](const TString& query, EStatus status, const TString& error = "") { + Cerr << "Check query:\n" << query << "\n"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), status); + if (status != EStatus::SUCCESS) { + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), error); + } + }; + + auto checkDisabled = [checkQuery](const TString& query) { + checkQuery(query, EStatus::GENERIC_ERROR, "Resource pool classifiers are disabled. Please contact your system administrator to enable it"); + }; + + // CREATE RESOURCE POOL CLASSIFIER + checkDisabled(R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL="test_pool" + );)"); + + // ALTER RESOURCE POOL CLASSIFIER + checkDisabled(R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (RANK = 1, RESOURCE_POOL = "test"), + RESET (MEMBERNAME); + )"); + + // DROP RESOURCE POOL CLASSIFIER + checkQuery("DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier;", + EStatus::GENERIC_ERROR, + "Classifier with name MyResourcePoolClassifier not found in database /Root"); + } + + Y_UNIT_TEST(DisableResourcePoolClassifiersOnServerless) { + auto ydb = NWorkload::TYdbSetupSettings() + .CreateSampleTenants(true) + .EnableResourcePoolsOnServerless(false) + .Create(); + + auto checkDisabled = [](const auto& result) { + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Resource pool classifiers are disabled for serverless domains. Please contact your system administrator to enable it"); + }; + + auto checkNotFound = [](const auto& result) { + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Classifier with name MyResourcePoolClassifier not found in database"); + }; + + const auto& createSql = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL="test_pool" + );)"; + + const auto& alterSql = R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (RANK = 1, RESOURCE_POOL = "test"), + RESET (MEMBERNAME); + )"; + + const auto& dropSql = "DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier;"; + + auto settings = NWorkload::TQueryRunnerSettings().PoolId(""); + + // Dedicated, enabled + settings.Database(ydb->GetSettings().GetDedicatedTenantName()).NodeIndex(1); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(createSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(alterSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(dropSql, settings)); + + // Shared, enabled + settings.Database(ydb->GetSettings().GetSharedTenantName()).NodeIndex(2); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(createSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(alterSql, settings)); + NWorkload::TSampleQueries::CheckSuccess(ydb->ExecuteQuery(dropSql, settings)); + + // Serverless, disabled + settings.Database(ydb->GetSettings().GetServerlessTenantName()).NodeIndex(2); + checkDisabled(ydb->ExecuteQuery(createSql, settings)); + checkDisabled(ydb->ExecuteQuery(alterSql, settings)); + checkNotFound(ydb->ExecuteQuery(dropSql, settings)); + } + + Y_UNIT_TEST(ResourcePoolClassifiersValidation) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + auto result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test", + ANOTHER_PROPERTY=20 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Unknown property: another_property"); + + result = session.ExecuteSchemeQuery(R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (ANOTHER_PROPERTY = 5), + RESET (SOME_PROPERTY); + )").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Unknown property: another_property, some_property"); + + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test", + RANK="StringValue" + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Failed to parse property rank:"); + + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK="0" + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Missing required property resource_pool"); + + result = session.ExecuteSchemeQuery(R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + RESET (RESOURCE_POOL); + )").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Cannot reset required property resource_pool"); + + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER `MyResource/PoolClassifier` WITH ( + RESOURCE_POOL="test" + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Symbol '/' is not allowed in the resource pool classifier name 'MyResource/PoolClassifier'"); + + result = session.ExecuteSchemeQuery(TStringBuilder() << R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test", + MEMBERNAME=")" << BUILTIN_ACL_METADATA << R"(" + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Invalid resource pool classifier configuration, cannot create classifier for system user " << BUILTIN_ACL_METADATA); + } + + Y_UNIT_TEST(ResourcePoolClassifiersRankValidation) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + // Create with sample rank + auto result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER ClassifierRank42 WITH ( + RESOURCE_POOL="test_pool", + RANK=42 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToOneLineString()); + + // Try to create with same rank + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER AnotherClassifierRank42 WITH ( + RESOURCE_POOL="test_pool", + RANK=42 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Resource pool classifier rank check failed, status: ALREADY_EXISTS, reason: {
: Error: Classifier with rank 42 already exists, its name ClassifierRank42 }"); + + // Create with high rank + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER `ClassifierRank2^63` WITH ( + RESOURCE_POOL="test_pool", + RANK=9223372036854775807 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToOneLineString()); + + // Try to create with auto rank + result = session.ExecuteSchemeQuery(R"( + CREATE RESOURCE POOL CLASSIFIER ClassifierRankAuto WITH ( + RESOURCE_POOL="test_pool", + MEMBERNAME="test@user" + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "The rank could not be set automatically, the maximum rank of the resource pool classifier is too high: 9223372036854775807"); + + // Try to alter to exist rank + result = session.ExecuteSchemeQuery(R"( + ALTER RESOURCE POOL CLASSIFIER `ClassifierRank2^63` SET ( + RANK=42 + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Resource pool classifier rank check failed, status: ALREADY_EXISTS, reason: {
: Error: Classifier with rank 42 already exists, its name ClassifierRank42 }"); + + // Try to reset classifier rank + result = session.ExecuteSchemeQuery(R"( + ALTER RESOURCE POOL CLASSIFIER ClassifierRank42 RESET ( + RANK + );)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "The rank could not be set automatically, the maximum rank of the resource pool classifier is too high: 9223372036854775807"); + } + + TString FetchResourcePoolClassifiers(TKikimrRunner& kikimr) { + auto& runtime = *kikimr.GetTestServer().GetRuntime(); + const TActorId edgeActor = runtime.AllocateEdgeActor(); + runtime.Send(NMetadata::NProvider::MakeServiceId(runtime.GetNodeId()), edgeActor, new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); + + const auto response = runtime.GrabEdgeEvent(edgeActor); + UNIT_ASSERT(response); + return response->Get()->GetSnapshotAs()->SerializeToString(); + } + + Y_UNIT_TEST(CreateResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + // Explicit rank + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL="test_pool", + MEMBERNAME="test@user" + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"membername\":\"test@user\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + + // Auto rank + query = R"( + CREATE RESOURCE POOL CLASSIFIER AnotherResourcePoolClassifier WITH ( + RESOURCE_POOL="test_pool", + MEMBERNAME="another@user" + );)"; + result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"membername\":\"test@user\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"},{\"rank\":1020,\"name\":\"AnotherResourcePoolClassifier\",\"config\":{\"membername\":\"another@user\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + + Y_UNIT_TEST(DoubleCreateResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + { + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test_pool", + RANK=20 + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test_pool", + RANK=1 + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Conflict with existing key"); + } + } + + Y_UNIT_TEST(AlterResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + // Create sample pool + { + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL="test_pool" + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + + // Test update one property + { + auto query = R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (MEMBERNAME = "test@user") + )"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"membername\":\"test@user\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + + // Create another pool + { + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER AnotherResourcePoolClassifier WITH ( + RESOURCE_POOL="test_pool", + RANK=42 + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"membername\":\"test@user\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"},{\"rank\":42,\"name\":\"AnotherResourcePoolClassifier\",\"config\":{\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + + // Test reset + { + auto query = R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + RESET (RANK, MEMBERNAME); + )"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":1042,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"membername\":\"\",\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"},{\"rank\":42,\"name\":\"AnotherResourcePoolClassifier\",\"config\":{\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + } + + Y_UNIT_TEST(AlterNonExistingResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + auto query = R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (RESOURCE_POOL = "test", RANK = 100), + RESET (MEMBERNAME); + )"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Classifier with name MyResourcePoolClassifier not found in database /Root"); + } + + Y_UNIT_TEST(DropResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + { + auto query = R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL="test_pool", + RANK=20 + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[{\"rank\":20,\"name\":\"MyResourcePoolClassifier\",\"config\":{\"resource_pool\":\"test_pool\"},\"database\":\"\\/Root\"}]}"); + } + + { + auto query = "DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + UNIT_ASSERT_VALUES_EQUAL(FetchResourcePoolClassifiers(kikimr), "{\"resource_pool_classifiers\":[]}"); + } + } + + Y_UNIT_TEST(DropNonExistingResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + TKikimrRunner kikimr(NKqp::TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + auto query = "DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier;"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Classifier with name MyResourcePoolClassifier not found in database /Root"); + } } Y_UNIT_TEST_SUITE(KqpOlapScheme) { diff --git a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp index f6b812e2f5dc..55d8662da6f4 100644 --- a/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp +++ b/ydb/core/kqp/ut/service/kqp_qs_queries_ut.cpp @@ -268,6 +268,47 @@ Y_UNIT_TEST_SUITE(KqpQueryService) { } } + Y_UNIT_TEST(ExecuteQueryWithResourcePoolClassifier) { + NKikimrConfig::TAppConfig config; + config.MutableFeatureFlags()->SetEnableResourcePools(true); + + auto kikimr = TKikimrRunner(TKikimrSettings() + .SetAppConfig(config) + .SetEnableResourcePools(true)); + auto db = kikimr.GetQueryClient(); + + const TString userSID = TStringBuilder() << "test@" << BUILTIN_ACL_DOMAIN; + const TString schemeSql = TStringBuilder() << R"( + CREATE RESOURCE POOL MyPool WITH ( + CONCURRENT_QUERY_LIMIT=0 + ); + CREATE RESOURCE POOL CLASSIFIER MyPoolClassifier WITH ( + RESOURCE_POOL="MyPool", + MEMBERNAME=")" << userSID << R"(" + ); + GRANT ALL ON `/Root` TO `)" << userSID << R"(`; + )"; + auto schemeResult = db.ExecuteQuery(schemeSql, TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(schemeResult.GetStatus(), EStatus::SUCCESS, schemeResult.GetIssues().ToString()); + + auto testUserClient = kikimr.GetQueryClient(TClientSettings().AuthToken(userSID)); + const TDuration timeout = TDuration::Seconds(5); + const TInstant start = TInstant::Now(); + while (TInstant::Now() - start <= timeout) { + const TString query = "SELECT 42;"; + auto result = testUserClient.ExecuteQuery(query, TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + if (!result.IsSuccess()) { + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::PRECONDITION_FAILED, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Resource pool MyPool was disabled due to zero concurrent query limit"); + return; + } + + Cerr << "Wait resource pool classifier " << TInstant::Now() - start << ": status = " << result.GetStatus() << ", issues = " << result.GetIssues().ToOneLineString() << "\n"; + Sleep(TDuration::Seconds(1)); + } + UNIT_ASSERT_C(false, "Waiting resource pool classifier timeout. Spent time " << TInstant::Now() - start << " exceeds limit " << timeout); + } + std::pair CalcRowsAndBatches(TExecuteQueryIterator& it) { ui32 totalRows = 0; ui32 totalBatches = 0; diff --git a/ydb/core/kqp/workload_service/actors/actors.h b/ydb/core/kqp/workload_service/actors/actors.h index c575842faf78..6e55f7553f55 100644 --- a/ydb/core/kqp/workload_service/actors/actors.h +++ b/ydb/core/kqp/workload_service/actors/actors.h @@ -16,7 +16,7 @@ NActors::IActor* CreatePoolFetcherActor(const NActors::TActorId& replyActorId, c NActors::IActor* CreatePoolCreatorActor(const NActors::TActorId& replyActorId, const TString& database, const TString& poolId, const NResourcePool::TPoolSettings& poolConfig, TIntrusiveConstPtr userToken, NACLibProto::TDiffACL diffAcl); // Checks that database is serverless -NActors::IActor* CreateDatabaseFetcherActor(const NActors::TActorId& replyActorId, const TString& database); +NActors::IActor* CreateDatabaseFetcherActor(const NActors::TActorId& replyActorId, const TString& database, TIntrusiveConstPtr userToken = nullptr, NACLib::EAccessRights checkAccess = NACLib::EAccessRights::NoAccess); // Cpu load fetcher actor NActors::IActor* CreateCpuLoadFetcherActor(const NActors::TActorId& replyActorId); diff --git a/ydb/core/kqp/workload_service/actors/pool_handlers_acors.cpp b/ydb/core/kqp/workload_service/actors/pool_handlers_acors.cpp index 921944a0db41..2ff6e6912abe 100644 --- a/ydb/core/kqp/workload_service/actors/pool_handlers_acors.cpp +++ b/ydb/core/kqp/workload_service/actors/pool_handlers_acors.cpp @@ -66,12 +66,14 @@ class TPoolHandlerActorBase : public TActor { LoadCpuThreshold->Set(std::max(poolConfig.DatabaseLoadCpuThreshold, 0.0)); } - void OnCleanup() { + void OnCleanup(bool resetConfigCounters) { ActivePoolHandlers->Dec(); - InFlightLimit->Set(0); - QueueSizeLimit->Set(0); - LoadCpuThreshold->Set(0); + if (resetConfigCounters) { + InFlightLimit->Set(0); + QueueSizeLimit->Set(0); + LoadCpuThreshold->Set(0); + } } private: @@ -136,8 +138,9 @@ class TPoolHandlerActorBase : public TActor { STRICT_STFUNC(StateFuncBase, // Workload service events sFunc(TEvents::TEvPoison, HandlePoison); - sFunc(TEvPrivate::TEvStopPoolHandler, HandleStop); + hFunc(TEvPrivate::TEvStopPoolHandler, Handle); hFunc(TEvPrivate::TEvResolvePoolResponse, Handle); + hFunc(TEvPrivate::TEvUpdatePoolSubscription, Handle); // Pool handler events hFunc(TEvPrivate::TEvCancelRequest, Handle); @@ -157,9 +160,9 @@ class TPoolHandlerActorBase : public TActor { this->Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvWatchRemove(0)); } - SendPoolInfoUpdate(std::nullopt, std::nullopt); + SendPoolInfoUpdate(std::nullopt, std::nullopt, Subscribers); - Counters.OnCleanup(); + Counters.OnCleanup(ResetCountersOnStrop); TBase::PassAway(); } @@ -170,8 +173,9 @@ class TPoolHandlerActorBase : public TActor { this->PassAway(); } - void HandleStop() { + void Handle(TEvPrivate::TEvStopPoolHandler::TPtr& ev) { LOG_I("Got stop pool handler request, waiting for " << LocalSessions.size() << " requests"); + ResetCountersOnStrop = ev->Get()->ResetCounters; if (LocalSessions.empty()) { PassAway(); } else { @@ -180,6 +184,8 @@ class TPoolHandlerActorBase : public TActor { } void Handle(TEvPrivate::TEvResolvePoolResponse::TPtr& ev) { + this->Send(MakeKqpWorkloadServiceId(this->SelfId().NodeId()), new TEvPrivate::TEvPlaceRequestIntoPoolResponse(Database, PoolId)); + auto event = std::move(ev->Get()->Event); const TActorId& workerActorId = event->Sender; if (!InFlightLimit) { @@ -204,8 +210,6 @@ class TPoolHandlerActorBase : public TActor { UpdatePoolConfig(ev->Get()->PoolConfig); UpdateSchemeboardSubscription(ev->Get()->PathId); OnScheduleRequest(request); - - this->Send(MakeKqpWorkloadServiceId(this->SelfId().NodeId()), new TEvPrivate::TEvPlaceRequestIntoPoolResponse(Database, PoolId)); } void Handle(TEvCleanupRequest::TPtr& ev) { @@ -241,6 +245,14 @@ class TPoolHandlerActorBase : public TActor { OnCleanupRequest(request); } + void Handle(TEvPrivate::TEvUpdatePoolSubscription::TPtr& ev) { + const auto& newSubscribers = ev->Get()->Subscribers; + if (!UpdateSchemeboardSubscription(ev->Get()->PathId)) { + SendPoolInfoUpdate(PoolConfig, SecurityObject, newSubscribers); + } + Subscribers.insert(newSubscribers.begin(), newSubscribers.end()); + } + void Handle(TEvTxProxySchemeCache::TEvWatchNotifyUpdated::TPtr& ev) { if (ev->Get()->Key != WatchKey) { // Skip old paths watch notifications @@ -265,12 +277,11 @@ class TPoolHandlerActorBase : public TActor { UpdatePoolConfig(poolConfig); const auto& pathDescription = result->GetPathDescription().GetSelf(); - NACLib::TSecurityObject object(pathDescription.GetOwner(), false); - if (object.MutableACL()->ParseFromString(pathDescription.GetEffectiveACL())) { - SendPoolInfoUpdate(poolConfig, object); - } else { - SendPoolInfoUpdate(poolConfig, std::nullopt); + SecurityObject = NACLib::TSecurityObject(pathDescription.GetOwner(), false); + if (!SecurityObject->MutableACL()->ParseFromString(pathDescription.GetEffectiveACL())) { + SecurityObject = std::nullopt; } + SendPoolInfoUpdate(poolConfig, SecurityObject, Subscribers); } void Handle(TEvTxProxySchemeCache::TEvWatchNotifyDeleted::TPtr& ev) { @@ -280,7 +291,7 @@ class TPoolHandlerActorBase : public TActor { } LOG_D("Got delete notification"); - SendPoolInfoUpdate(std::nullopt, std::nullopt); + SendPoolInfoUpdate(std::nullopt, std::nullopt, Subscribers); } public: @@ -324,7 +335,7 @@ class TPoolHandlerActorBase : public TActor { if (!request->Started && request->State != TRequest::EState::Finishing) { if (request->State == TRequest::EState::Canceling && status == Ydb::StatusIds::SUCCESS) { status = Ydb::StatusIds::CANCELLED; - issues.AddIssue(TStringBuilder() << "Delay deadline exceeded in pool " << PoolId); + issues.AddIssue(TStringBuilder() << "Request was delayed during " << TInstant::Now() - request->StartTime << ", that is larger than delay deadline " << PoolConfig.QueryCancelAfter << " in pool " << PoolId << ", request was canceled"); } ReplyContinue(request, status, issues); return; @@ -346,8 +357,10 @@ class TPoolHandlerActorBase : public TActor { RemoveRequest(request); } - void SendPoolInfoUpdate(const std::optional& config, const std::optional& securityObject) const { - this->Send(MakeKqpProxyID(this->SelfId().NodeId()), new TEvUpdatePoolInfo(Database, PoolId, config, securityObject)); + void SendPoolInfoUpdate(const std::optional& config, const std::optional& securityObject, const std::unordered_set& subscribers) const { + for (const auto& subscriber : subscribers) { + this->Send(subscriber, new TEvUpdatePoolInfo(Database, PoolId, config, securityObject)); + } } protected: @@ -437,9 +450,9 @@ class TPoolHandlerActorBase : public TActor { LOG_I("Cancel request for worker " << request->WorkerActorId << ", session id: " << request->SessionId << ", local in flight: " << LocalInFlight); } - void UpdateSchemeboardSubscription(TPathId pathId) { + bool UpdateSchemeboardSubscription(TPathId pathId) { if (WatchPathId && *WatchPathId == pathId) { - return; + return false; } if (WatchPathId) { @@ -452,6 +465,7 @@ class TPoolHandlerActorBase : public TActor { LOG_D("Subscribed on schemeboard notifications for path: " << pathId.ToString()); WatchPathId = std::make_unique(pathId); this->Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvWatchPathId(*WatchPathId, WatchKey)); + return true; } void UpdatePoolConfig(const NResourcePool::TPoolSettings& poolConfig) { @@ -493,15 +507,18 @@ class TPoolHandlerActorBase : public TActor { private: NResourcePool::TPoolSettings PoolConfig; + std::optional SecurityObject; // Scheme board settings std::unique_ptr WatchPathId; ui64 WatchKey = 0; + std::unordered_set Subscribers; // Pool state ui64 LocalInFlight = 0; std::unordered_map LocalSessions; bool StopHandler = false; // Stop than all requests finished + bool ResetCountersOnStrop = true; }; @@ -609,8 +626,13 @@ class TFifoPoolHandlerActor : public TPoolHandlerActorBase= MAX_PENDING_REQUESTS || SaturationSub(GetLocalSessionsCount() - GetLocalInFlight(), InFlightLimit) > QueueSizeLimit) { - ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Too many pending requests for pool " << PoolId); + if (PendingRequests.size() >= MAX_PENDING_REQUESTS) { + ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Request was rejected, number of local pending requests is " << PendingRequests.size() << ", that is larger than allowed limit " << MAX_PENDING_REQUESTS); + return; + } + + if (SaturationSub(GetLocalSessionsCount() - GetLocalInFlight(), InFlightLimit) > QueueSizeLimit) { + ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Request was rejected, number of local pending/delayed requests is " << GetLocalSessionsCount() - GetLocalInFlight() << ", that is larger than allowed limit " << QueueSizeLimit << " (including concurrent query limit " << InFlightLimit << ") for pool " << PoolId); return; } @@ -729,15 +751,15 @@ class TFifoPoolHandlerActor : public TPoolHandlerActorBase QueueSizeLimit) { RemoveBackRequests(PendingRequests, std::min(delayedRequests - QueueSizeLimit, PendingRequests.size()), [this](TRequest* request) { - ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Too many pending requests for pool " << PoolId); + ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Request was rejected, number of local pending requests is " << PendingRequests.size() << ", number of global delayed/running requests is " << GlobalState.AmountRequests() << ", sum of them is larger than allowed limit " << QueueSizeLimit << " (including concurrent query limit " << InFlightLimit << ") for pool " << PoolId); }); FifoCounters.PendingRequestsCount->Set(PendingRequests.size()); } if (PendingRequests.empty() && delayedRequestsCount > QueueSizeLimit) { - RemoveBackRequests(DelayedRequests, delayedRequestsCount - QueueSizeLimit, [this](TRequest* request) { + RemoveBackRequests(DelayedRequests, delayedRequestsCount - QueueSizeLimit, [this, delayedRequestsCount](TRequest* request) { AddFinishedRequest(request->SessionId); - ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Too many pending requests for pool " << PoolId); + ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Request was rejected, number of local delayed requests is " << delayedRequestsCount << ", that is larger than allowed limit " << QueueSizeLimit << " for pool " << PoolId); }); } @@ -774,9 +796,10 @@ class TFifoPoolHandlerActor : public TPoolHandlerActorBaseGet()->QuotaAccepted) { LOG_D("Skipped request start due to load cpu threshold"); if (static_cast(ev->Cookie) == EStartRequestCase::Pending) { - ForEachUnfinished(DelayedRequests.begin(), DelayedRequests.end(), [this](TRequest* request) { + NYql::TIssues issues = GroupIssues(ev->Get()->Issues, TStringBuilder() << "Request was rejected, failed to request CPU quota for pool " << PoolId << ", current CPU threshold is " << 100.0 * ev->Get()->MaxClusterLoad << "%"); + ForEachUnfinished(DelayedRequests.begin(), DelayedRequests.end(), [this, issues](TRequest* request) { AddFinishedRequest(request->SessionId); - ReplyContinue(request, Ydb::StatusIds::OVERLOADED, TStringBuilder() << "Too many pending requests for pool " << PoolId); + ReplyContinue(request, Ydb::StatusIds::OVERLOADED, issues); }); } RefreshState(); diff --git a/ydb/core/kqp/workload_service/actors/scheme_actors.cpp b/ydb/core/kqp/workload_service/actors/scheme_actors.cpp index 7647bc4f348d..781d9f4a6eca 100644 --- a/ydb/core/kqp/workload_service/actors/scheme_actors.cpp +++ b/ydb/core/kqp/workload_service/actors/scheme_actors.cpp @@ -179,8 +179,8 @@ class TPoolFetcherActor : public TSchemeActorBase { void StartRequest() override { LOG_D("Start pool fetching"); auto event = NTableCreator::BuildSchemeCacheNavigateRequest( - {{".resource_pools", PoolId}}, - Database, + {{".metadata/workload_manager/pools", PoolId}}, + Database ? Database : AppData()->TenantName, UserToken ); event->ResultSet[0].Access |= NACLib::SelectRow; @@ -221,7 +221,7 @@ class TPoolFetcherActor : public TSchemeActorBase { } Issues.AddIssues(std::move(issues)); - Send(ReplyActorId, new TEvPrivate::TEvFetchPoolResponse(status, PoolConfig, PathIdFromPathId(PathId), std::move(Issues))); + Send(ReplyActorId, new TEvPrivate::TEvFetchPoolResponse(status, Database, PoolId, PoolConfig, PathIdFromPathId(PathId), std::move(Issues))); PassAway(); } @@ -326,7 +326,7 @@ class TPoolCreatorActor : public TSchemeActorBase { auto event = std::make_unique(); auto& schemeTx = *event->Record.MutableTransaction()->MutableModifyScheme(); - schemeTx.SetWorkingDir(JoinPath({Database, ".resource_pools"})); + schemeTx.SetWorkingDir(JoinPath({Database ? Database : AppData()->TenantName, ".metadata/workload_manager/pools"})); schemeTx.SetOperationType(NKikimrSchemeOp::ESchemeOpCreateResourcePool); schemeTx.SetInternal(true); @@ -390,10 +390,10 @@ class TPoolCreatorActor : public TSchemeActorBase { void BuildCreatePoolRequest(NKikimrSchemeOp::TResourcePoolDescription& poolDescription) { poolDescription.SetName(PoolId); - for (auto& [property, value] : NResourcePool::GetPropertiesMap(PoolConfig)) { + for (auto& [property, value] : PoolConfig.GetPropertiesMap()) { poolDescription.MutableProperties()->MutableProperties()->insert({ property, - std::visit(NResourcePool::TSettingsExtractor{}, value) + std::visit(NResourcePool::TPoolSettings::TExtractor{}, value) }); } } @@ -445,9 +445,11 @@ class TPoolCreatorActor : public TSchemeActorBase { class TDatabaseFetcherActor : public TSchemeActorBase { public: - TDatabaseFetcherActor(const TActorId& replyActorId, const TString& database) + TDatabaseFetcherActor(const TActorId& replyActorId, const TString& database, TIntrusiveConstPtr userToken, NACLib::EAccessRights checkAccess) : ReplyActorId(replyActorId) , Database(database) + , UserToken(userToken) + , CheckAccess(checkAccess) {} void DoBootstrap() { @@ -467,11 +469,13 @@ class TDatabaseFetcherActor : public TSchemeActorBase { case EStatus::PathNotTable: case EStatus::PathNotPath: case EStatus::RedirectLookupError: - case EStatus::AccessDenied: case EStatus::RootUnknown: case EStatus::PathErrorUnknown: Reply(Ydb::StatusIds::NOT_FOUND, TStringBuilder() << "Database " << Database << " not found or you don't have access permissions"); return; + case EStatus::AccessDenied: + Reply(Ydb::StatusIds::UNAUTHORIZED, TStringBuilder() << "You don't have access permissions for database " << Database); + return; case EStatus::LookupError: case EStatus::TableCreationNotComplete: if (!ScheduleRetry(TStringBuilder() << "Retry error " << result.Status)) { @@ -479,7 +483,13 @@ class TDatabaseFetcherActor : public TSchemeActorBase { } return; case EStatus::Ok: - Serverless = result.DomainInfo && result.DomainInfo->IsServerless(); + if (!IsSubDomainPath(result)) { + Reply(Ydb::StatusIds::UNSUPPORTED, TStringBuilder() << "Invalid database path " << Database << ", please check the correctness of the path"); + return; + } + if (result.DomainInfo) { + Serverless = result.DomainInfo->IsServerless(); + } Reply(Ydb::StatusIds::SUCCESS); return; } @@ -496,8 +506,13 @@ class TDatabaseFetcherActor : public TSchemeActorBase { protected: void StartRequest() override { LOG_D("Start database fetching"); - auto event = NTableCreator::BuildSchemeCacheNavigateRequest({{}}, Database, nullptr); + auto event = NTableCreator::BuildSchemeCacheNavigateRequest( + {{}}, + Database ? Database : AppData()->TenantName, + UserToken + ); event->ResultSet[0].Operation = NSchemeCache::TSchemeCacheNavigate::OpPath; + event->ResultSet[0].Access |= CheckAccess; Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(event.Release()), IEventHandle::FlagTrackDelivery); } @@ -526,9 +541,23 @@ class TDatabaseFetcherActor : public TSchemeActorBase { PassAway(); } + static bool IsSubDomainPath(const NSchemeCache::TSchemeCacheNavigate::TEntry& entry) { + switch (entry.Kind) { + case NSchemeCache::TSchemeCacheNavigate::EKind::KindSubdomain: + case NSchemeCache::TSchemeCacheNavigate::EKind::KindExtSubdomain: + return true; + case NSchemeCache::TSchemeCacheNavigate::EKind::KindPath: + return entry.Self->Info.GetPathId() == NSchemeShard::RootPathId; + default: + return false; + } + } + private: const TActorId ReplyActorId; const TString Database; + const TIntrusiveConstPtr UserToken; + const NACLib::EAccessRights CheckAccess; bool Serverless = false; }; @@ -547,8 +576,8 @@ IActor* CreatePoolCreatorActor(const TActorId& replyActorId, const TString& data return new TPoolCreatorActor(replyActorId, database, poolId, poolConfig, userToken, diffAcl); } -IActor* CreateDatabaseFetcherActor(const TActorId& replyActorId, const TString& database) { - return new TDatabaseFetcherActor(replyActorId, database); +IActor* CreateDatabaseFetcherActor(const TActorId& replyActorId, const TString& database, TIntrusiveConstPtr userToken, NACLib::EAccessRights checkAccess) { + return new TDatabaseFetcherActor(replyActorId, database, userToken, checkAccess); } } // NKikimr::NKqp::NWorkload diff --git a/ydb/core/kqp/workload_service/common/events.h b/ydb/core/kqp/workload_service/common/events.h index a0db39a644b5..48643582d7f0 100644 --- a/ydb/core/kqp/workload_service/common/events.h +++ b/ydb/core/kqp/workload_service/common/events.h @@ -30,6 +30,7 @@ struct TEvPrivate { EvResignPoolHandler, EvStopPoolHandler, EvCancelRequest, + EvUpdatePoolSubscription, EvCpuQuotaRequest, EvCpuQuotaResponse, @@ -45,6 +46,8 @@ struct TEvPrivate { EvStartRequestResponse, EvCleanupRequestsResponse, + EvRanksCheckerResponse, + EvEnd }; @@ -73,14 +76,18 @@ struct TEvPrivate { }; struct TEvFetchPoolResponse : public NActors::TEventLocal { - TEvFetchPoolResponse(Ydb::StatusIds::StatusCode status, const NResourcePool::TPoolSettings& poolConfig, TPathId pathId, NYql::TIssues issues) + TEvFetchPoolResponse(Ydb::StatusIds::StatusCode status, const TString& database, const TString& poolId, const NResourcePool::TPoolSettings& poolConfig, TPathId pathId, NYql::TIssues issues) : Status(status) + , Database(database) + , PoolId(poolId) , PoolConfig(poolConfig) , PathId(pathId) , Issues(std::move(issues)) {} const Ydb::StatusIds::StatusCode Status; + const TString Database; + const TString PoolId; const NResourcePool::TPoolSettings PoolConfig; const TPathId PathId; const NYql::TIssues Issues; @@ -159,6 +166,11 @@ struct TEvPrivate { }; struct TEvStopPoolHandler : public NActors::TEventLocal { + explicit TEvStopPoolHandler(bool resetCounters) + : ResetCounters(resetCounters) + {} + + const bool ResetCounters; }; struct TEvCancelRequest : public NActors::TEventLocal { @@ -169,6 +181,16 @@ struct TEvPrivate { const TString SessionId; }; + struct TEvUpdatePoolSubscription : public NActors::TEventLocal { + explicit TEvUpdatePoolSubscription(TPathId pathId, const std::unordered_set& subscribers) + : PathId(pathId) + , Subscribers(subscribers) + {} + + const TPathId PathId; + const std::unordered_set Subscribers; + }; + // Cpu load requests struct TEvCpuQuotaRequest : public NActors::TEventLocal { explicit TEvCpuQuotaRequest(double maxClusterLoad) @@ -179,11 +201,15 @@ struct TEvPrivate { }; struct TEvCpuQuotaResponse : public NActors::TEventLocal { - explicit TEvCpuQuotaResponse(bool quotaAccepted) + explicit TEvCpuQuotaResponse(bool quotaAccepted, double maxClusterLoad, NYql::TIssues issues) : QuotaAccepted(quotaAccepted) + , MaxClusterLoad(maxClusterLoad) + , Issues(std::move(issues)) {} const bool QuotaAccepted; + const double MaxClusterLoad; + const NYql::TIssues Issues; }; struct TEvCpuLoadResponse : public NActors::TEventLocal { @@ -295,6 +321,21 @@ struct TEvPrivate { const std::vector SesssionIds; const NYql::TIssues Issues; }; + + // Resource pool classifier events + struct TEvRanksCheckerResponse : public TEventLocal { + TEvRanksCheckerResponse(Ydb::StatusIds::StatusCode status, i64 maxRank, ui64 numberClassifiers, NYql::TIssues issues) + : Status(status) + , MaxRank(maxRank) + , NumberClassifiers(numberClassifiers) + , Issues(std::move(issues)) + {} + + const Ydb::StatusIds::StatusCode Status; + const i64 MaxRank; + const ui64 NumberClassifiers; + const NYql::TIssues Issues; + }; }; } // NKikimr::NKqp::NWorkload diff --git a/ydb/core/kqp/workload_service/common/helpers.cpp b/ydb/core/kqp/workload_service/common/helpers.cpp index f1893bf8b92f..79fd3fff5c69 100644 --- a/ydb/core/kqp/workload_service/common/helpers.cpp +++ b/ydb/core/kqp/workload_service/common/helpers.cpp @@ -12,12 +12,7 @@ NYql::TIssues GroupIssues(const NYql::TIssues& issues, const TString& message) { } void ParsePoolSettings(const NKikimrSchemeOp::TResourcePoolDescription& description, NResourcePool::TPoolSettings& poolConfig) { - const auto& properties = description.GetProperties().GetProperties(); - for (auto& [property, value] : NResourcePool::GetPropertiesMap(poolConfig)) { - if (auto propertyIt = properties.find(property); propertyIt != properties.end()) { - std::visit(NResourcePool::TSettingsParser{propertyIt->second}, value); - } - } + poolConfig = NResourcePool::TPoolSettings(description.GetProperties().GetProperties()); } ui64 SaturationSub(ui64 x, ui64 y) { diff --git a/ydb/core/kqp/workload_service/kqp_workload_service.cpp b/ydb/core/kqp/workload_service/kqp_workload_service.cpp index 295e536d2499..24fdd21e3d63 100644 --- a/ydb/core/kqp/workload_service/kqp_workload_service.cpp +++ b/ydb/core/kqp/workload_service/kqp_workload_service.cpp @@ -74,6 +74,7 @@ class TKqpWorkloadService : public TActorBootstrapped { EnabledResourcePools = AppData()->FeatureFlags.GetEnableResourcePools(); EnabledResourcePoolsOnServerless = AppData()->FeatureFlags.GetEnableResourcePoolsOnServerless(); + EnableResourcePoolsCounters = AppData()->FeatureFlags.GetEnableResourcePoolsCounters(); if (EnabledResourcePools) { InitializeWorkloadService(); } @@ -101,6 +102,7 @@ class TKqpWorkloadService : public TActorBootstrapped { EnabledResourcePools = event.GetConfig().GetFeatureFlags().GetEnableResourcePools(); EnabledResourcePoolsOnServerless = event.GetConfig().GetFeatureFlags().GetEnableResourcePoolsOnServerless(); + EnableResourcePoolsCounters = event.GetConfig().GetFeatureFlags().GetEnableResourcePoolsCounters(); if (EnabledResourcePools) { LOG_I("Resource pools was enanbled"); InitializeWorkloadService(); @@ -140,6 +142,18 @@ class TKqpWorkloadService : public TActorBootstrapped { } } + void Handle(TEvSubscribeOnPoolChanges::TPtr& ev) { + const TString& database = ev->Get()->Database; + const TString& poolId = ev->Get()->PoolId; + if (!EnabledResourcePools) { + Send(ev->Sender, new TEvUpdatePoolInfo(database, poolId, std::nullopt, std::nullopt)); + return; + } + + LOG_D("Recieved subscription request, Database: " << database << ", PoolId: " << poolId); + GetOrCreateDatabaseState(database)->DoSubscribeRequest(std::move(ev)); + } + void Handle(TEvPlaceRequestIntoPool::TPtr& ev) { const TActorId& workerActorId = ev->Sender; if (!EnabledResourcePools) { @@ -188,11 +202,13 @@ class TKqpWorkloadService : public TActorBootstrapped { hFunc(TEvInterconnect::TEvNodesInfo, Handle); hFunc(TEvents::TEvUndelivered, Handle); + hFunc(TEvSubscribeOnPoolChanges, Handle); hFunc(TEvPlaceRequestIntoPool, Handle); hFunc(TEvCleanupRequest, Handle); hFunc(TEvents::TEvWakeup, Handle); hFunc(TEvPrivate::TEvFetchDatabaseResponse, Handle); + hFunc(TEvPrivate::TEvFetchPoolResponse, Handle); hFunc(TEvPrivate::TEvResolvePoolResponse, Handle); hFunc(TEvPrivate::TEvPlaceRequestIntoPoolResponse, Handle); hFunc(TEvPrivate::TEvNodesInfoRequest, Handle); @@ -211,6 +227,21 @@ class TKqpWorkloadService : public TActorBootstrapped { GetOrCreateDatabaseState(ev->Get()->Database)->UpdateDatabaseInfo(ev); } + void Handle(TEvPrivate::TEvFetchPoolResponse::TPtr& ev) { + const TString& database = ev->Get()->Database; + const TString& poolId = ev->Get()->PoolId; + + TActorId poolHandler; + if (ev->Get()->Status == Ydb::StatusIds::SUCCESS) { + LOG_D("Successfully fetched pool " << poolId << ", Database: " << database); + poolHandler = GetOrCreatePoolState(database, poolId, ev->Get()->PoolConfig)->PoolHandler; + } else { + LOG_W("Failed to fetch pool " << poolId << ", Database: " << database << ", status: " << ev->Get()->Status << ", issues: " << ev->Get()->Issues.ToOneLineString()); + } + + GetOrCreateDatabaseState(database)->UpdatePoolInfo(ev, poolHandler); + } + void Handle(TEvPrivate::TEvResolvePoolResponse::TPtr& ev) { const auto& event = ev->Get()->Event; const TString& database = event->Get()->Database; @@ -226,18 +257,7 @@ class TKqpWorkloadService : public TActorBootstrapped { LOG_D("Successfully fetched pool " << poolId << ", Database: " << database << ", SessionId: " << event->Get()->SessionId); - auto poolState = GetPoolState(database, poolId); - if (!poolState) { - TString poolKey = GetPoolKey(database, poolId); - LOG_I("Creating new handler for pool " << poolKey); - - auto poolHandler = Register(CreatePoolHandlerActor(database, poolId, ev->Get()->PoolConfig, Counters.Counters)); - poolState = &PoolIdToState.insert({poolKey, TPoolState{.PoolHandler = poolHandler, .ActorContext = ActorContext()}}).first->second; - - Counters.ActivePools->Inc(); - ScheduleIdleCheck(); - } - + auto poolState = GetOrCreatePoolState(database, poolId, ev->Get()->PoolConfig); poolState->PendingRequests.emplace(std::move(ev)); poolState->StartPlaceRequest(); } @@ -361,7 +381,7 @@ class TKqpWorkloadService : public TActorBootstrapped { if (auto poolState = GetPoolState(database, poolId)) { if (poolState->NewPoolHandler) { - Send(*poolState->NewPoolHandler, new TEvPrivate::TEvStopPoolHandler()); + Send(*poolState->NewPoolHandler, new TEvPrivate::TEvStopPoolHandler(false)); } poolState->NewPoolHandler = ev->Get()->NewHandler; poolState->UpdateHandler(); @@ -423,7 +443,7 @@ class TKqpWorkloadService : public TActorBootstrapped { for (const auto& [poolKey, poolState] : PoolIdToState) { if (!poolState.InFlightRequests && TInstant::Now() - poolState.LastUpdateTime > IDLE_DURATION) { CpuQuotaManager->CleanupHandler(poolState.PoolHandler); - Send(poolState.PoolHandler, new TEvPrivate::TEvStopPoolHandler()); + Send(poolState.PoolHandler, new TEvPrivate::TEvStopPoolHandler(true)); poolsToDelete.emplace_back(poolKey); } } @@ -500,6 +520,23 @@ class TKqpWorkloadService : public TActorBootstrapped { return &DatabaseToState.insert({database, TDatabaseState{.ActorContext = ActorContext(), .EnabledResourcePoolsOnServerless = EnabledResourcePoolsOnServerless}}).first->second; } + TPoolState* GetOrCreatePoolState(const TString& database, const TString& poolId, const NResourcePool::TPoolSettings& poolConfig) { + const auto& poolKey = GetPoolKey(database, poolId); + if (auto poolState = GetPoolState(poolKey)) { + return poolState; + } + + LOG_I("Creating new handler for pool " << poolKey); + + const auto poolHandler = Register(CreatePoolHandlerActor(database, poolId, poolConfig, EnableResourcePoolsCounters ? Counters.Counters : MakeIntrusive())); + const auto poolState = &PoolIdToState.insert({poolKey, TPoolState{.PoolHandler = poolHandler, .ActorContext = ActorContext()}}).first->second; + + Counters.ActivePools->Inc(); + ScheduleIdleCheck(); + + return poolState; + } + TPoolState* GetPoolState(const TString& database, const TString& poolId) { return GetPoolState(GetPoolKey(database, poolId)); } @@ -525,6 +562,7 @@ class TKqpWorkloadService : public TActorBootstrapped { bool EnabledResourcePools = false; bool EnabledResourcePoolsOnServerless = false; + bool EnableResourcePoolsCounters = false; bool ServiceInitialized = false; bool IdleChecksStarted = false; ETablesCreationStatus TablesCreationStatus = ETablesCreationStatus::Cleanup; diff --git a/ydb/core/kqp/workload_service/kqp_workload_service_impl.h b/ydb/core/kqp/workload_service/kqp_workload_service_impl.h index 8503a4fb7949..9ae115235a25 100644 --- a/ydb/core/kqp/workload_service/kqp_workload_service_impl.h +++ b/ydb/core/kqp/workload_service/kqp_workload_service_impl.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -18,28 +19,65 @@ struct TDatabaseState { bool& EnabledResourcePoolsOnServerless; std::vector PendingRequersts = {}; + std::unordered_map> PendingSubscriptions = {}; bool HasDefaultPool = false; bool Serverless = false; + bool DatabaseUnsupported = false; TInstant LastUpdateTime = TInstant::Zero(); + void DoSubscribeRequest(TEvSubscribeOnPoolChanges::TPtr ev) { + const TString& poolId = ev->Get()->PoolId; + auto& subscribers = PendingSubscriptions[poolId]; + if (subscribers.empty()) { + ActorContext.Register(CreatePoolFetcherActor(ActorContext.SelfID, ev->Get()->Database, poolId, nullptr)); + } + + subscribers.emplace(ev->Sender); + } + void DoPlaceRequest(TEvPlaceRequestIntoPool::TPtr ev) { TString database = ev->Get()->Database; PendingRequersts.emplace_back(std::move(ev)); if (!EnabledResourcePoolsOnServerless && (TInstant::Now() - LastUpdateTime) > IDLE_DURATION) { ActorContext.Register(CreateDatabaseFetcherActor(ActorContext.SelfID, database)); - } else { + } else if (!DatabaseUnsupported) { StartPendingRequests(); + } else { + ReplyContinueError(Ydb::StatusIds::UNSUPPORTED, {NYql::TIssue(TStringBuilder() << "Unsupported database: " << database)}); + } + } + + void UpdatePoolInfo(const TEvPrivate::TEvFetchPoolResponse::TPtr& ev, NActors::TActorId poolHandler) { + const TString& poolId = ev->Get()->PoolId; + auto& subscribers = PendingSubscriptions[poolId]; + if (subscribers.empty()) { + return; + } + + if (ev->Get()->Status == Ydb::StatusIds::SUCCESS && poolHandler) { + ActorContext.Send(poolHandler, new TEvPrivate::TEvUpdatePoolSubscription(ev->Get()->PathId, subscribers)); + } else { + const TString& database = ev->Get()->Database; + for (const auto& subscriber : subscribers) { + ActorContext.Send(subscriber, new TEvUpdatePoolInfo(database, poolId, std::nullopt, std::nullopt)); + } } + subscribers.clear(); } void UpdateDatabaseInfo(const TEvPrivate::TEvFetchDatabaseResponse::TPtr& ev) { + DatabaseUnsupported = ev->Get()->Status == Ydb::StatusIds::UNSUPPORTED; if (ev->Get()->Status != Ydb::StatusIds::SUCCESS) { ReplyContinueError(ev->Get()->Status, GroupIssues(ev->Get()->Issues, "Failed to fetch database info")); return; } + if (Serverless != ev->Get()->Serverless) { + ActorContext.Send(MakeKqpProxyID(ActorContext.SelfID.NodeId()), new TEvUpdateDatabaseInfo(ev->Get()->Database, ev->Get()->Serverless)); + } + LastUpdateTime = TInstant::Now(); Serverless = ev->Get()->Serverless; StartPendingRequests(); @@ -83,7 +121,7 @@ struct TPoolState { return; } - ActorContext.Send(PoolHandler, new TEvPrivate::TEvStopPoolHandler()); + ActorContext.Send(PoolHandler, new TEvPrivate::TEvStopPoolHandler(false)); PoolHandler = *NewPoolHandler; NewPoolHandler = std::nullopt; InFlightRequests = 0; @@ -122,7 +160,7 @@ struct TCpuQuotaManagerState { auto response = CpuQuotaManager.RequestCpuQuota(0.0, maxClusterLoad); bool quotaAccepted = response.Status == NYdb::EStatus::SUCCESS; - ActorContext.Send(poolHandler, new TEvPrivate::TEvCpuQuotaResponse(quotaAccepted), 0, coockie); + ActorContext.Send(poolHandler, new TEvPrivate::TEvCpuQuotaResponse(quotaAccepted, maxClusterLoad, std::move(response.Issues)), 0, coockie); // Schedule notification if (!quotaAccepted) { diff --git a/ydb/core/kqp/workload_service/tables/table_queries.cpp b/ydb/core/kqp/workload_service/tables/table_queries.cpp index d498c778d224..ba6457288c94 100644 --- a/ydb/core/kqp/workload_service/tables/table_queries.cpp +++ b/ydb/core/kqp/workload_service/tables/table_queries.cpp @@ -178,8 +178,15 @@ class TCleanupTablesActor : public TSchemeActorBase { TablePathsToCheck.clear(); for (const auto& result : results) { - const TString& path = CanonizePath(result.Path); - LOG_D("Describe table " << path << " status " << result.Status); + const TString& fullPath = CanonizePath(result.Path); + LOG_D("Describe table " << fullPath << " status " << result.Status); + + std::pair pathPair; + if (TString error; !TrySplitPathByDb(fullPath, AppData()->TenantName, pathPair, error)) { + TablesExists = false; + AddError(TStringBuilder() << "Failed to describe table path " << fullPath << ", " << error); + continue; + } switch (result.Status) { case EStatus::Unknown: @@ -188,10 +195,10 @@ class TCleanupTablesActor : public TSchemeActorBase { case EStatus::AccessDenied: case EStatus::RedirectLookupError: TablesExists = false; - AddError(TStringBuilder() << "Failed to describe table path " << path << ", " << result.Status); + AddError(TStringBuilder() << "Failed to describe table path " << fullPath << ", " << result.Status); break; case EStatus::LookupError: - RetryPathCheck(result.Path, result.Status); + RetryPathCheck(pathPair.second, result.Status); break; case EStatus::RootUnknown: case EStatus::PathErrorUnknown: @@ -199,9 +206,9 @@ class TCleanupTablesActor : public TSchemeActorBase { TablesExists = false; break; case EStatus::Ok: - LOG_D("Start cleanup for table " << path); + LOG_D("Start cleanup for table " << fullPath); CleanupQueriesInFlight++; - Register(new TCleanupTablesRetryQuery(SelfId(), path)); + Register(new TCleanupTablesRetryQuery(SelfId(), fullPath)); break; } } @@ -251,14 +258,14 @@ class TCleanupTablesActor : public TSchemeActorBase { } private: - void RetryPathCheck(const TVector& path, EStatus status) { - if (TablePathsToCheck.empty() && !ScheduleRetry(TStringBuilder() << "Retry " << status << " for table " << CanonizePath(path))) { + void RetryPathCheck(const TString& path, EStatus status) { + if (TablePathsToCheck.empty() && !ScheduleRetry(TStringBuilder() << "Retry " << status << " for table " << path)) { TablesExists = false; - AddError(TStringBuilder() << "Retry limit exceeded for table " << CanonizePath(path) << ", " << status); + AddError(TStringBuilder() << "Retry limit exceeded for table " << path << ", " << status); return; } - TablePathsToCheck.emplace_back(path); + TablePathsToCheck.emplace_back(SplitPath(path)); } template diff --git a/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.cpp b/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.cpp index 15d525a67630..0a9f29ccf23e 100644 --- a/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.cpp +++ b/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.cpp @@ -231,6 +231,8 @@ class TWorkloadServiceYdbSetup : public IYdbSetup { TAppConfig appConfig; appConfig.MutableFeatureFlags()->SetEnableResourcePools(Settings_.EnableResourcePools_); appConfig.MutableFeatureFlags()->SetEnableMetadataObjectsOnServerless(Settings_.EnableMetadataObjectsOnServerless_); + appConfig.MutableFeatureFlags()->SetEnableResourcePoolsOnServerless(Settings_.EnableResourcePoolsOnServerless_); + appConfig.MutableFeatureFlags()->SetEnableResourcePoolsCounters(true); return appConfig; } @@ -323,15 +325,8 @@ class TWorkloadServiceYdbSetup : public IYdbSetup { return; } - NResourcePool::TPoolSettings poolConfig; - poolConfig.ConcurrentQueryLimit = Settings_.ConcurrentQueryLimit_; - poolConfig.QueueSize = Settings_.QueueSize_; - poolConfig.QueryCancelAfter = Settings_.QueryCancelAfter_; - poolConfig.QueryMemoryLimitPercentPerNode = Settings_.QueryMemoryLimitPercentPerNode_; - poolConfig.DatabaseLoadCpuThreshold = Settings_.DatabaseLoadCpuThreshold_; - TActorId edgeActor = GetRuntime()->AllocateEdgeActor(); - GetRuntime()->Register(CreatePoolCreatorActor(edgeActor, Settings_.DomainName_, Settings_.PoolId_, poolConfig, nullptr, {})); + GetRuntime()->Register(CreatePoolCreatorActor(edgeActor, Settings_.DomainName_, Settings_.PoolId_, Settings_.GetDefaultPoolSettings(), nullptr, {})); auto response = GetRuntime()->GrabEdgeEvent(edgeActor, FUTURE_WAIT_TIMEOUT); UNIT_ASSERT_VALUES_EQUAL_C(response->Get()->Status, Ydb::StatusIds::SUCCESS, response->Get()->Issues.ToOneLineString()); } @@ -401,7 +396,7 @@ class TWorkloadServiceYdbSetup : public IYdbSetup { auto token = NACLib::TUserToken(userSID, {}); WaitFor(FUTURE_WAIT_TIMEOUT, "pool acl", [this, token, access, poolId](TString& errorString) { - auto response = Navigate(TStringBuilder() << ".resource_pools/" << (poolId ? poolId : Settings_.PoolId_)); + auto response = Navigate(TStringBuilder() << ".metadata/workload_manager/pools/" << (poolId ? poolId : Settings_.PoolId_)); if (!response) { errorString = "empty response"; return false; @@ -520,15 +515,17 @@ class TWorkloadServiceYdbSetup : public IYdbSetup { } std::unique_ptr GetQueryRequest(const TString& query, const TQueryRunnerSettings& settings) const { + UNIT_ASSERT_C(settings.PoolId_, "Query pool id is not specified"); + auto event = std::make_unique(); - event->Record.SetUserToken(NACLib::TUserToken("", settings.UserSID_, {}).SerializeAsString()); + event->Record.SetUserToken(NACLib::TUserToken("", settings.UserSID_, settings.GroupSIDs_).SerializeAsString()); auto request = event->Record.MutableRequest(); request->SetQuery(query); request->SetType(NKikimrKqp::QUERY_TYPE_SQL_GENERIC_QUERY); request->SetAction(NKikimrKqp::QUERY_ACTION_EXECUTE); - request->SetDatabase(Settings_.DomainName_); - request->SetPoolId(settings.PoolId_); + request->SetDatabase(settings.Database_ ? settings.Database_ : Settings_.DomainName_); + request->SetPoolId(*settings.PoolId_); return event; } @@ -617,6 +614,16 @@ bool TQueryRunnerResultAsync::HasValue() const { //// TYdbSetupSettings +NResourcePool::TPoolSettings TYdbSetupSettings::GetDefaultPoolSettings() const { + NResourcePool::TPoolSettings poolConfig; + poolConfig.ConcurrentQueryLimit = ConcurrentQueryLimit_; + poolConfig.QueueSize = QueueSize_; + poolConfig.QueryCancelAfter = QueryCancelAfter_; + poolConfig.QueryMemoryLimitPercentPerNode = QueryMemoryLimitPercentPerNode_; + poolConfig.DatabaseLoadCpuThreshold = DatabaseLoadCpuThreshold_; + return poolConfig; +} + TIntrusivePtr TYdbSetupSettings::Create() const { return MakeIntrusive(*this); } diff --git a/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.h b/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.h index 24f56c3214bc..c07a43797a49 100644 --- a/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.h +++ b/ydb/core/kqp/workload_service/ut/common/kqp_workload_service_ut_common.h @@ -14,7 +14,7 @@ namespace NKikimr::NKqp::NWorkload { -inline constexpr TDuration FUTURE_WAIT_TIMEOUT = TDuration::Seconds(30); +inline constexpr TDuration FUTURE_WAIT_TIMEOUT = TDuration::Seconds(60); // Query runner @@ -24,8 +24,9 @@ struct TQueryRunnerSettings { // Query settings FLUENT_SETTING_DEFAULT(ui32, NodeIndex, 0); - FLUENT_SETTING_DEFAULT(TString, PoolId, ""); + FLUENT_SETTING_DEFAULT(std::optional, PoolId, std::nullopt); FLUENT_SETTING_DEFAULT(TString, UserSID, "user@" BUILTIN_SYSTEM_DOMAIN); + FLUENT_SETTING_DEFAULT(TVector, GroupSIDs, {}); FLUENT_SETTING_DEFAULT(TString, Database, ""); // Runner settings @@ -70,6 +71,7 @@ struct TYdbSetupSettings { FLUENT_SETTING_DEFAULT(bool, CreateSampleTenants, false); FLUENT_SETTING_DEFAULT(bool, EnableResourcePools, true); FLUENT_SETTING_DEFAULT(bool, EnableMetadataObjectsOnServerless, true); + FLUENT_SETTING_DEFAULT(bool, EnableResourcePoolsOnServerless, false); // Default pool settings FLUENT_SETTING_DEFAULT(TString, PoolId, "sample_pool_id"); @@ -79,6 +81,7 @@ struct TYdbSetupSettings { FLUENT_SETTING_DEFAULT(double, QueryMemoryLimitPercentPerNode, -1); FLUENT_SETTING_DEFAULT(double, DatabaseLoadCpuThreshold, -1); + NResourcePool::TPoolSettings GetDefaultPoolSettings() const; TIntrusivePtr Create() const; TString GetDedicatedTenantName() const; @@ -127,12 +130,6 @@ struct TSampleQueries { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::SUCCESS, result.GetIssues().ToString()); } - template - static void CheckOverloaded(const TResult& result, const TString& poolId) { - UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::OVERLOADED, result.GetIssues().ToString()); - UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Too many pending requests for pool " << poolId); - } - template static void CheckCancelled(const TResult& result) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::CANCELLED, result.GetIssues().ToString()); diff --git a/ydb/core/kqp/workload_service/ut/kqp_workload_service_actors_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_workload_service_actors_ut.cpp index 8d6880d3eb58..7d9db86cccae 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_workload_service_actors_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_workload_service_actors_ut.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -57,7 +58,7 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceActors) { const TString& userSID = "user@test"; TSampleQueries::CheckSuccess(ydb->ExecuteQuery(TStringBuilder() << R"( - GRANT DESCRIBE SCHEMA ON `/Root/.resource_pools/)" << ydb->GetSettings().PoolId_ << "` TO `" << userSID << "`;" + GRANT DESCRIBE SCHEMA ON `/Root/.metadata/workload_manager/pools/)" << ydb->GetSettings().PoolId_ << "` TO `" << userSID << "`;" )); ydb->WaitPoolAccess(userSID, NACLib::EAccessRights::DescribeSchema); @@ -66,7 +67,7 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceActors) { UNIT_ASSERT_STRING_CONTAINS(failedResponse->Get()->Issues.ToString(), TStringBuilder() << "You don't have access permissions for resource pool " << ydb->GetSettings().PoolId_); TSampleQueries::CheckSuccess(ydb->ExecuteQuery(TStringBuilder() << R"( - GRANT SELECT ROW ON `/Root/.resource_pools/)" << ydb->GetSettings().PoolId_ << "` TO `" << userSID << "`;" + GRANT SELECT ROW ON `/Root/.metadata/workload_manager/pools/)" << ydb->GetSettings().PoolId_ << "` TO `" << userSID << "`;" )); ydb->WaitPoolAccess(userSID, NACLib::EAccessRights::SelectRow); @@ -85,7 +86,7 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceActors) { Y_UNIT_TEST(TestCreateDefaultPool) { auto ydb = TYdbSetupSettings().Create(); - const TString path = TStringBuilder() << ".resource_pools/" << NResourcePool::DEFAULT_POOL_ID; + const TString path = TStringBuilder() << ".metadata/workload_manager/pools/" << NResourcePool::DEFAULT_POOL_ID; auto response = ydb->Navigate(path, NSchemeCache::TSchemeCacheNavigate::EOp::OpUnknown); UNIT_ASSERT_VALUES_EQUAL(response->ErrorCount, 1); UNIT_ASSERT_VALUES_EQUAL(response->ResultSet.at(0).Kind, NSchemeCache::TSchemeCacheNavigate::EKind::KindUnknown); @@ -131,7 +132,7 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceActors) { // Check alter access TSampleQueries::CheckSuccess(ydb->ExecuteQuery(TStringBuilder() << R"( ALTER RESOURCE POOL )" << NResourcePool::DEFAULT_POOL_ID << R"( SET ( - QUEUE_SIZE=1 + QUERY_MEMORY_LIMIT_PERCENT_PER_NODE=1 ); )", settings)); @@ -164,4 +165,92 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceActors) { } } +Y_UNIT_TEST_SUITE(KqpWorkloadServiceSubscriptions) { + TActorId SubscribeOnPool(TIntrusivePtr ydb) { + const auto& settings = ydb->GetSettings(); + auto& runtime = *ydb->GetRuntime(); + const auto& edgeActor = runtime.AllocateEdgeActor(); + + runtime.Send(MakeKqpWorkloadServiceId(runtime.GetNodeId()), edgeActor, new TEvSubscribeOnPoolChanges(settings.DomainName_, settings.PoolId_)); + const auto& response = runtime.GrabEdgeEvent(edgeActor, FUTURE_WAIT_TIMEOUT); + UNIT_ASSERT_C(response, "Subscription update not found"); + + const auto& config = response->Get()->Config; + UNIT_ASSERT_C(config, "Pool config not found"); + UNIT_ASSERT_C(*config == settings.GetDefaultPoolSettings(), "Unexpected pool config"); + + const auto& securityObject = response->Get()->SecurityObject; + UNIT_ASSERT_C(securityObject, "Security object not found"); + UNIT_ASSERT_VALUES_EQUAL_C(securityObject->GetOwnerSID(), BUILTIN_ACL_ROOT, "Unexpected owner user SID"); + + return edgeActor; + } + + Y_UNIT_TEST(TestResourcePoolSubscription) { + auto ydb = TYdbSetupSettings() + .QueueSize(10) + .ConcurrentQueryLimit(5) + .QueryCancelAfter(TDuration::Seconds(42)) + .QueryMemoryLimitPercentPerNode(55.0) + .DatabaseLoadCpuThreshold(30.0) + .Create(); + + SubscribeOnPool(ydb); + } + + Y_UNIT_TEST(TestResourcePoolSubscriptionAfterAlter) { + auto ydb = TYdbSetupSettings().Create(); + + const auto& subscriber = SubscribeOnPool(ydb); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + ALTER RESOURCE POOL )" << ydb->GetSettings().PoolId_ << R"( SET ( + CONCURRENT_QUERY_LIMIT=42 + ); + )"); + + const auto& response = ydb->GetRuntime()->GrabEdgeEvent(subscriber, FUTURE_WAIT_TIMEOUT); + UNIT_ASSERT_C(response, "Subscription update not found"); + + const auto& config = response->Get()->Config; + UNIT_ASSERT_C(config, "Pool config not found"); + UNIT_ASSERT_VALUES_EQUAL(config->ConcurrentQueryLimit, 42); + } + + Y_UNIT_TEST(TestResourcePoolSubscriptionAfterAclChange) { + auto ydb = TYdbSetupSettings().Create(); + + const auto& subscriber = SubscribeOnPool(ydb); + + const TString& userSID = "test@user"; + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + GRANT ALL ON `/Root/.metadata/workload_manager/pools/)" << ydb->GetSettings().PoolId_ << R"(` TO `)" << userSID << R"(`; + )"); + + const auto& response = ydb->GetRuntime()->GrabEdgeEvent(subscriber, FUTURE_WAIT_TIMEOUT); + UNIT_ASSERT_C(response, "Subscription update not found"); + + const auto& securityObject = response->Get()->SecurityObject; + UNIT_ASSERT_C(securityObject, "Security object not found"); + + NACLib::TUserToken token("", userSID, {}); + UNIT_ASSERT_C(securityObject->CheckAccess(NACLib::GenericFull, token), TStringBuilder() << "Unexpected pool access rights: " << securityObject->ToString()); + } + + Y_UNIT_TEST(TestResourcePoolSubscriptionAfterDrop) { + auto ydb = TYdbSetupSettings().Create(); + + const auto& subscriber = SubscribeOnPool(ydb); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + DROP RESOURCE POOL )" << ydb->GetSettings().PoolId_ << R"(; + )"); + + const auto& response = ydb->GetRuntime()->GrabEdgeEvent(subscriber, FUTURE_WAIT_TIMEOUT); + UNIT_ASSERT_C(response, "Subscription update not found"); + UNIT_ASSERT_C(!response->Get()->Config, "Unexpected pool config"); + UNIT_ASSERT_C(!response->Get()->SecurityObject, "Unexpected security object"); + } +} + } // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/workload_service/ut/kqp_workload_service_tables_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_workload_service_tables_ut.cpp index 4d37370a8599..a004917a2a03 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_workload_service_tables_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_workload_service_tables_ut.cpp @@ -70,25 +70,24 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceTables) { CanonizePath({ydb->GetSettings().DomainName_, ".metadata/workload_manager"}) ).GetValue(FUTURE_WAIT_TIMEOUT); UNIT_ASSERT_VALUES_EQUAL_C(listResult.GetStatus(), NYdb::EStatus::SUCCESS, listResult.GetIssues().ToString()); - UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren().size(), 2); + UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren().size(), 3); } Y_UNIT_TEST(TestTablesIsNotCreatingForUnlimitedPool) { auto ydb = TYdbSetupSettings() .ConcurrentQueryLimit(-1) - .QueueSize(10) + .QueryMemoryLimitPercentPerNode(50) .Create(); TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query)); // Check that there is no .metadata folder auto listResult = ydb->GetSchemeClient().ListDirectory( - CanonizePath(ydb->GetSettings().DomainName_) + CanonizePath({ydb->GetSettings().DomainName_, ".metadata", "workload_manager"}) ).GetValue(FUTURE_WAIT_TIMEOUT); UNIT_ASSERT_VALUES_EQUAL_C(listResult.GetStatus(), NYdb::EStatus::SUCCESS, listResult.GetIssues().ToString()); - UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren().size(), 2); - UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren()[0].Name, ".resource_pools"); - UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren()[1].Name, ".sys"); + UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren().size(), 1); + UNIT_ASSERT_VALUES_EQUAL(listResult.GetChildren()[0].Name, "pools"); } Y_UNIT_TEST(TestPoolStateFetcherActor) { diff --git a/ydb/core/kqp/workload_service/ut/kqp_workload_service_ut.cpp b/ydb/core/kqp/workload_service/ut/kqp_workload_service_ut.cpp index 5dda602ba3fc..cad50ad143da 100644 --- a/ydb/core/kqp/workload_service/ut/kqp_workload_service_ut.cpp +++ b/ydb/core/kqp/workload_service/ut/kqp_workload_service_ut.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -47,6 +48,56 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().PoolId("another_pool_id"))); } + Y_UNIT_TEST(WorkloadServiceDisabledByFeatureFlagOnServerless) { + auto ydb = TYdbSetupSettings() + .CreateSampleTenants(true) + .EnableResourcePoolsOnServerless(false) + .Create(); + + const TString& poolId = "another_pool_id"; + auto settings = TQueryRunnerSettings().PoolId(poolId); + + // Dedicated, enabled + TSampleQueries::CheckNotFound(ydb->ExecuteQuery( + TSampleQueries::TSelect42::Query, + settings.Database(ydb->GetSettings().GetDedicatedTenantName()).NodeIndex(1) + ), poolId); + + // Shared, enabled + TSampleQueries::CheckNotFound(ydb->ExecuteQuery( + TSampleQueries::TSelect42::Query, + settings.Database(ydb->GetSettings().GetSharedTenantName()).NodeIndex(2) + ), poolId); + + // Serverless, disabled + TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery( + TSampleQueries::TSelect42::Query, + settings.Database(ydb->GetSettings().GetServerlessTenantName()).NodeIndex(2) + )); + } + + Y_UNIT_TEST(WorkloadServiceDisabledByInvalidDatabasePath) { + auto ydb = TYdbSetupSettings().Create(); + + const TString& poolId = "another_pool_id"; + auto settings = TQueryRunnerSettings().PoolId(poolId); + + TSampleQueries::CheckNotFound(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, settings), poolId); + + const TString& tabmleName = "sub_path"; + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + CREATE TABLE )" << tabmleName << R"( ( + Key Int32, + PRIMARY KEY (Key) + ); + )"); + + TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery( + TSampleQueries::TSelect42::Query, + settings.Database(TStringBuilder() << CanonizePath(ydb->GetSettings().DomainName_) << "/" << tabmleName) + )); + } + TQueryRunnerResultAsync StartQueueSizeCheckRequests(TIntrusivePtr ydb, const TQueryRunnerSettings& settings) { // One of these requests should be rejected by QueueSize auto firstRequest = ydb->ExecuteQueryAsync(TSampleQueries::TSelect42::Query, settings); @@ -58,7 +109,10 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { } UNIT_ASSERT_C(firstRequest.HasValue(), "One of two requests shoud be rejected"); UNIT_ASSERT_C(!secondRequest.HasValue(), "One of two requests shoud be placed in pool"); - TSampleQueries::CheckOverloaded(firstRequest.GetResult(), ydb->GetSettings().PoolId_); + + auto result = firstRequest.GetResult(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local pending requests is 2, number of global delayed/running requests is 1, sum of them is larger than allowed limit 1 (including concurrent query limit 1) for pool " << ydb->GetSettings().PoolId_); return secondRequest; } @@ -114,10 +168,9 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { auto hangingRequest = ydb->ExecuteQueryAsync(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().HangUpDuringExecution(true)); ydb->WaitQueryExecution(hangingRequest); - TSampleQueries::CheckOverloaded( - ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().ExecutionExpected(false)), - ydb->GetSettings().PoolId_ - ); + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().ExecutionExpected(false)); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local pending requests is 1, number of global delayed/running requests is 1, sum of them is larger than allowed limit 0 (including concurrent query limit 1) for pool " << ydb->GetSettings().PoolId_); ydb->ContinueQueryExecution(hangingRequest); TSampleQueries::TSelect42::CheckResult(hangingRequest.GetResult()); @@ -142,10 +195,9 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { ydb->WaitQueryExecution(asyncResult); } - TSampleQueries::CheckOverloaded( - ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().ExecutionExpected(false)), - ydb->GetSettings().PoolId_ - ); + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().ExecutionExpected(false)); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local pending requests is 1, number of global delayed/running requests is " << inFlight << ", sum of them is larger than allowed limit 0 (including concurrent query limit " << inFlight << ") for pool " << ydb->GetSettings().PoolId_); for (const auto& asyncResult : asyncResults) { ydb->ContinueQueryExecution(asyncResult); @@ -230,7 +282,8 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().ExecutionExpected(false)); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::CANCELLED, result.GetIssues().ToString()); - UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Delay deadline exceeded in pool " << ydb->GetSettings().PoolId_); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was delayed during"); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << ", that is larger than delay deadline 10.000000s in pool " << ydb->GetSettings().PoolId_ << ", request was canceled"); } Y_UNIT_TEST(TestCpuLoadThresholdRefresh) { @@ -259,10 +312,12 @@ Y_UNIT_TEST_SUITE(KqpWorkloadService) { TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query)); TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().PoolId(NResourcePool::DEFAULT_POOL_ID))); - ydb->WaitPoolHandlersCount(0, 2, TDuration::Seconds(95)); + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + DROP RESOURCE POOL )" << ydb->GetSettings().PoolId_ << R"(; + DROP RESOURCE POOL )" << NResourcePool::DEFAULT_POOL_ID << R"(; + )"); - // Check pool creation after cleanup - TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query)); + ydb->WaitPoolHandlersCount(0, std::nullopt, TDuration::Seconds(95)); } } @@ -287,7 +342,9 @@ Y_UNIT_TEST_SUITE(KqpWorkloadServiceDistributed) { ydb->WaitPoolState({.DelayedRequests = 1, .RunningRequests = 1}); // Check distributed queue size - TSampleQueries::CheckOverloaded(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().NodeIndex(0)), ydb->GetSettings().PoolId_); + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().NodeIndex(0)); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local pending requests is 1, number of global delayed/running requests is 2, sum of them is larger than allowed limit 1 (including concurrent query limit 1) for pool " << ydb->GetSettings().PoolId_); ydb->ContinueQueryExecution(delayedRequest); ydb->ContinueQueryExecution(hangingRequest); @@ -357,7 +414,9 @@ Y_UNIT_TEST_SUITE(ResourcePoolsDdl) { ); ydb->WaitQueryExecution(hangingRequest); - TSampleQueries::CheckOverloaded(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().PoolId(poolId)), poolId); + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().PoolId(poolId)); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local pending requests is 1, number of global delayed/running requests is 1, sum of them is larger than allowed limit 0 (including concurrent query limit 1) for pool " << poolId); ydb->ContinueQueryExecution(hangingRequest); TSampleQueries::TSelect42::CheckResult(hangingRequest.GetResult()); @@ -399,7 +458,10 @@ Y_UNIT_TEST_SUITE(ResourcePoolsDdl) { QUEUE_SIZE=0 ); )"); - TSampleQueries::CheckOverloaded(delayedRequest.GetResult(), ydb->GetSettings().PoolId_); + + auto result = delayedRequest.GetResult(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::OVERLOADED, result.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "Request was rejected, number of local delayed requests is 1, that is larger than allowed limit 0 for pool " << ydb->GetSettings().PoolId_); ydb->ContinueQueryExecution(hangingRequest); TSampleQueries::TSelect42::CheckResult(hangingRequest.GetResult()); @@ -478,7 +540,7 @@ Y_UNIT_TEST_SUITE(ResourcePoolsDdl) { ); IYdbSetup::WaitFor(FUTURE_WAIT_TIMEOUT, "pool drop", [ydb, poolId](TString& errorString) { - auto kind = ydb->Navigate(TStringBuilder() << ".resource_pools/" << poolId)->ResultSet.at(0).Kind; + auto kind = ydb->Navigate(TStringBuilder() << ".metadata/workload_manager/pools/" << poolId)->ResultSet.at(0).Kind; errorString = TStringBuilder() << "kind = " << kind; return kind == NSchemeCache::TSchemeCacheNavigate::EKind::KindUnknown; @@ -498,7 +560,7 @@ Y_UNIT_TEST_SUITE(ResourcePoolsDdl) { CREATE RESOURCE POOL )" << poolId << R"( WITH ( CONCURRENT_QUERY_LIMIT=1 ); - GRANT DESCRIBE SCHEMA ON `/Root/.resource_pools/)" << poolId << "` TO `" << userSID << "`;" + GRANT DESCRIBE SCHEMA ON `/Root/.metadata/workload_manager/pools/)" << poolId << "` TO `" << userSID << "`;" ); ydb->WaitPoolAccess(userSID, NACLib::EAccessRights::DescribeSchema, poolId); @@ -508,11 +570,224 @@ Y_UNIT_TEST_SUITE(ResourcePoolsDdl) { UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), TStringBuilder() << "You don't have access permissions for resource pool " << poolId); ydb->ExecuteSchemeQuery(TStringBuilder() << R"( - GRANT SELECT ROW ON `/Root/.resource_pools/)" << poolId << "` TO `" << userSID << "`;" + GRANT SELECT ROW ON `/Root/.metadata/workload_manager/pools/)" << poolId << "` TO `" << userSID << "`;" ); ydb->WaitPoolAccess(userSID, NACLib::EAccessRights::SelectRow, poolId); TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, settings)); } } +Y_UNIT_TEST_SUITE(ResourcePoolClassifiersDdl) { + Y_UNIT_TEST(TestResourcePoolClassifiersPermissions) { + auto ydb = TYdbSetupSettings().Create(); + + const TString& userSID = "user@test"; + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + GRANT DESCRIBE SCHEMA ON `/Root` TO `)" << userSID << R"(`; + GRANT DESCRIBE SCHEMA, SELECT ROW ON `/Root/.metadata/workload_manager/pools/)" << ydb->GetSettings().PoolId_ << "` TO `" << userSID << "`;" + ); + ydb->WaitPoolAccess(userSID, NACLib::EAccessRights::DescribeSchema | NACLib::EAccessRights::SelectRow); + + auto settings = TQueryRunnerSettings().UserSID(userSID); + + ydb->WaitFor(TDuration::Seconds(5), "Database permissions", [ydb, settings](TString& errorString) { + auto result = ydb->ExecuteQuery("DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier", settings); + + errorString = result.GetIssues().ToOneLineString(); + return result.GetStatus() == EStatus::GENERIC_ERROR && errorString.Contains("You don't have access permissions for database Root"); + }); + + auto createResult = ydb->ExecuteQuery(TStringBuilder() << R"( + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RESOURCE_POOL=")" << NResourcePool::DEFAULT_POOL_ID << R"(", + RANK=20 + ); + )", settings); + UNIT_ASSERT_VALUES_EQUAL_C(createResult.GetStatus(), EStatus::GENERIC_ERROR, createResult.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(createResult.GetIssues().ToOneLineString(), "You don't have access permissions for database Root"); + + auto alterResult = ydb->ExecuteQuery(R"( + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier SET ( + RANK=20 + ); + )", settings); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::GENERIC_ERROR, alterResult.GetIssues().ToOneLineString()); + UNIT_ASSERT_STRING_CONTAINS(alterResult.GetIssues().ToOneLineString(), "You don't have access permissions for database Root"); + } + + void CreateSampleResourcePoolClassifier(TIntrusivePtr ydb, const TString& classifierId, const TString& userSID, const TString& poolId) { + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + GRANT ALL ON `/Root` TO `)" << userSID << R"(`; + CREATE RESOURCE POOL )" << poolId << R"( WITH ( + CONCURRENT_QUERY_LIMIT=0 + ); + CREATE RESOURCE POOL CLASSIFIER )" << classifierId << R"( WITH ( + RESOURCE_POOL=")" << poolId << R"(", + MEMBERNAME=")" << userSID << R"(" + ); + )"); + } + + TString CreateSampleResourcePoolClassifier(TIntrusivePtr ydb, const TQueryRunnerSettings& settings, const TString& poolId) { + const TString& classifierId = "my_pool_classifier"; + CreateSampleResourcePoolClassifier(ydb, classifierId, settings.UserSID_, poolId); + return classifierId; + } + + void WaitForFail(TIntrusivePtr ydb, const TQueryRunnerSettings& settings, const TString& poolId) { + ydb->WaitFor(TDuration::Seconds(5), "Resource pool classifier fail", [ydb, settings, poolId](TString& errorString) { + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, settings); + + errorString = result.GetIssues().ToOneLineString(); + return result.GetStatus() == EStatus::PRECONDITION_FAILED && errorString.Contains(TStringBuilder() << "Resource pool " << poolId << " was disabled due to zero concurrent query limit"); + }); + } + + void WaitForSuccess(TIntrusivePtr ydb, const TQueryRunnerSettings& settings) { + ydb->WaitFor(TDuration::Seconds(5), "Resource pool classifier success", [ydb, settings](TString& errorString) { + auto result = ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, settings); + + errorString = result.GetIssues().ToOneLineString(); + return result.GetStatus() == EStatus::SUCCESS; + }); + } + + Y_UNIT_TEST(TestCreateResourcePoolClassifier) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + } + + Y_UNIT_TEST(TestAlterResourcePoolClassifier) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + const TString& classifierId = CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + ALTER RESOURCE POOL CLASSIFIER )" << classifierId << R"( SET ( + RESOURCE_POOL=")" << NResourcePool::DEFAULT_POOL_ID << R"(" + ); + )"); + + WaitForSuccess(ydb, settings); + } + + Y_UNIT_TEST(TestDropResourcePoolClassifier) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + const TString& classifierId = CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + DROP RESOURCE POOL CLASSIFIER )" << classifierId << R"(; + )"); + + WaitForSuccess(ydb, settings); + } + + Y_UNIT_TEST(TestDropResourcePool) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + DROP RESOURCE POOL )" << poolId << R"(; + )"); + + WaitForSuccess(ydb, settings); + } + + Y_UNIT_TEST(TestResourcePoolClassifierRanks) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + + const TString& classifierId = "rank_classifier"; + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + CREATE RESOURCE POOL CLASSIFIER )" << classifierId << R"( WITH ( + RANK="1", + RESOURCE_POOL=")" << NResourcePool::DEFAULT_POOL_ID << R"(", + MEMBERNAME=")" << settings.UserSID_ << R"(" + ); + )"); + + WaitForSuccess(ydb, settings); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + ALTER RESOURCE POOL CLASSIFIER )" << classifierId << R"( RESET ( + RANK + ); + )"); + + WaitForFail(ydb, settings, poolId); + } + + Y_UNIT_TEST(TestExplicitPoolId) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId("").UserSID("test@user"); + const TString& poolId = "my_pool"; + CreateSampleResourcePoolClassifier(ydb, settings, poolId); + + WaitForFail(ydb, settings, poolId); + TSampleQueries::TSelect42::CheckResult(ydb->ExecuteQuery(TSampleQueries::TSelect42::Query, TQueryRunnerSettings().PoolId(NResourcePool::DEFAULT_POOL_ID))); + } + + Y_UNIT_TEST(TestMultiGroupClassification) { + auto ydb = TYdbSetupSettings().Create(); + + auto settings = TQueryRunnerSettings().PoolId(""); + + const TString& poolId = "my_pool"; + const TString& firstSID = "first@user"; + const TString& secondSID = "second@user"; + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + CREATE RESOURCE POOL )" << poolId << R"( WITH ( + CONCURRENT_QUERY_LIMIT=0 + ); + CREATE RESOURCE POOL CLASSIFIER first_classifier WITH ( + RESOURCE_POOL=")" << poolId << R"(", + MEMBERNAME=")" << firstSID << R"(", + RANK=1 + ); + CREATE RESOURCE POOL CLASSIFIER second_classifier WITH ( + RESOURCE_POOL=")" << NResourcePool::DEFAULT_POOL_ID << R"(", + MEMBERNAME=")" << secondSID << R"(", + RANK=2 + ); + )"); + + WaitForFail(ydb, settings.GroupSIDs({firstSID}), poolId); + WaitForSuccess(ydb, settings.GroupSIDs({secondSID})); + WaitForFail(ydb, settings.GroupSIDs({firstSID, secondSID}), poolId); + + ydb->ExecuteSchemeQuery(TStringBuilder() << R"( + ALTER RESOURCE POOL CLASSIFIER second_classifier SET ( + RANK=0 + ); + )"); + + WaitForSuccess(ydb, settings.GroupSIDs({firstSID, secondSID})); + } +} + } // namespace NKikimr::NKqp diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings.cpp b/ydb/core/resource_pools/resource_pool_classifier_settings.cpp new file mode 100644 index 000000000000..34ce0e30e0ed --- /dev/null +++ b/ydb/core/resource_pools/resource_pool_classifier_settings.cpp @@ -0,0 +1,49 @@ +#include "resource_pool_classifier_settings.h" + +#include + + +namespace NKikimr::NResourcePool { + +//// TClassifierSettings::TParser + +void TClassifierSettings::TParser::operator()(i64* setting) const { + *setting = FromString(Value); + if (*setting < -1) { + throw yexception() << "Invalid integer value " << *setting << ", it is should be greater or equal -1"; + } +} + +void TClassifierSettings::TParser::operator()(TString* setting) const { + *setting = Value; +} + +//// TClassifierSettings::TExtractor + +TString TClassifierSettings::TExtractor::operator()(i64* setting) const { + return ToString(*setting); +} + +TString TClassifierSettings::TExtractor::operator()(TString* setting) const { + return *setting; +} + +//// TPoolSettings + +std::unordered_map TClassifierSettings::GetPropertiesMap() { + std::unordered_map properties = { + {"rank", &Rank}, + {"resource_pool", &ResourcePool}, + {"membername", &Membername} + }; + return properties; +} + +void TClassifierSettings::Validate() const { + NACLib::TUserToken token(Membername, TVector{}); + if (token.IsSystemUser()) { + throw yexception() << "Invalid resource pool classifier configuration, cannot create classifier for system user " << Membername; + } +} + +} // namespace NKikimr::NResourcePool diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings.h b/ydb/core/resource_pools/resource_pool_classifier_settings.h new file mode 100644 index 000000000000..2decfc1a581a --- /dev/null +++ b/ydb/core/resource_pools/resource_pool_classifier_settings.h @@ -0,0 +1,37 @@ +#pragma once + +#include "resource_pool_settings.h" + +#include + + +namespace NKikimr::NResourcePool { + +inline constexpr i64 CLASSIFIER_RANK_OFFSET = 1000; +inline constexpr i64 CLASSIFIER_COUNT_LIMIT = 1000; + +struct TClassifierSettings : public TSettingsBase { + using TBase = TSettingsBase; + using TProperty = std::variant; + + struct TParser : public TBase::TParser { + void operator()(i64* setting) const; + void operator()(TString* setting) const; + }; + + struct TExtractor : public TBase::TExtractor { + TString operator()(i64* setting) const; + TString operator()(TString* setting) const; + }; + + bool operator==(const TClassifierSettings& other) const = default; + + std::unordered_map GetPropertiesMap(); + void Validate() const; + + i64 Rank = -1; // -1 = max rank + CLASSIFIER_RANK_OFFSET + TString ResourcePool = DEFAULT_POOL_ID; + TString Membername = ""; +}; + +} // namespace NKikimr::NResourcePool diff --git a/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp b/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp new file mode 100644 index 000000000000..a91933a96603 --- /dev/null +++ b/ydb/core/resource_pools/resource_pool_classifier_settings_ut.cpp @@ -0,0 +1,60 @@ +#include "resource_pool_classifier_settings.h" + +#include + +#include + + +namespace NKikimr { + +using namespace NResourcePool; + + +Y_UNIT_TEST_SUITE(ResourcePoolClassifierTest) { + Y_UNIT_TEST(IntSettingsParsing) { + TClassifierSettings settings; + auto propertiesMap = settings.GetPropertiesMap(); + + std::visit(TClassifierSettings::TParser{"0"}, propertiesMap["rank"]); + UNIT_ASSERT_VALUES_EQUAL(settings.Rank, 0); + + std::visit(TClassifierSettings::TParser{"123"}, propertiesMap["rank"]); + UNIT_ASSERT_VALUES_EQUAL(settings.Rank, 123); + + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TClassifierSettings::TParser{"string_value"}, propertiesMap["rank"]), TFromStringException, "Unexpected symbol \"s\" at pos 0 in string \"string_value\"."); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TClassifierSettings::TParser{"9223372036854775808"}, propertiesMap["rank"]), TFromStringException, "Integer overflow in string \"9223372036854775808\"."); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TClassifierSettings::TParser{"-2"}, propertiesMap["rank"]), yexception, "Invalid integer value -2, it is should be greater or equal -1"); + } + + Y_UNIT_TEST(StringSettingsParsing) { + TClassifierSettings settings; + auto propertiesMap = settings.GetPropertiesMap(); + + std::visit(TClassifierSettings::TParser{"test_pool"}, propertiesMap["resource_pool"]); + UNIT_ASSERT_VALUES_EQUAL(settings.ResourcePool, "test_pool"); + + std::visit(TClassifierSettings::TParser{"test@user"}, propertiesMap["membername"]); + UNIT_ASSERT_VALUES_EQUAL(settings.Membername, "test@user"); + } + + Y_UNIT_TEST(SettingsExtracting) { + TClassifierSettings settings; + settings.Rank = 123; + settings.ResourcePool = "test_pool"; + settings.Membername = "test@user"; + auto propertiesMap = settings.GetPropertiesMap(); + + TClassifierSettings::TExtractor extractor; + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["rank"]), "123"); + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["resource_pool"]), "test_pool"); + UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["membername"]), "test@user"); + } + + Y_UNIT_TEST(SettingsValidation) { + TClassifierSettings settings; + settings.Membername = BUILTIN_ACL_METADATA; + UNIT_ASSERT_EXCEPTION_CONTAINS(settings.Validate(), yexception, TStringBuilder() << "Invalid resource pool classifier configuration, cannot create classifier for system user " << settings.Membername); + } +} + +} // namespace NKikimr diff --git a/ydb/core/resource_pools/resource_pool_settings.cpp b/ydb/core/resource_pools/resource_pool_settings.cpp index f477d334c625..c11f653999d0 100644 --- a/ydb/core/resource_pools/resource_pool_settings.cpp +++ b/ydb/core/resource_pools/resource_pool_settings.cpp @@ -3,17 +3,74 @@ namespace NKikimr::NResourcePool { -std::unordered_map GetPropertiesMap(TPoolSettings& settings, bool restricted) { +//// TPoolSettings::TParser + +void TPoolSettings::TParser::operator()(i32* setting) const { + *setting = FromString(Value); + if (*setting < -1) { + throw yexception() << "Invalid integer value " << *setting << ", it is should be greater or equal -1"; + } +} + +void TPoolSettings::TParser::operator()(TDuration* setting) const { + ui64 seconds = FromString(Value); + if (seconds > std::numeric_limits::max() / 1000) { + throw yexception() << "Invalid seconds value " << seconds << ", it is should be less or equal than " << std::numeric_limits::max() / 1000; + } + *setting = TDuration::Seconds(seconds); +} + +void TPoolSettings::TParser::operator()(TPercent* setting) const { + *setting = FromString(Value); + if (*setting != -1 && (*setting < 0 || 100 < *setting)) { + throw yexception() << "Invalid percent value " << *setting << ", it is should be between 0 and 100 or -1"; + } +} + +//// TPoolSettings::TExtractor + +TString TPoolSettings::TExtractor::operator()(i32* setting) const { + return ToString(*setting); +} + +TString TPoolSettings::TExtractor::operator()(double* setting) const { + return ToString(*setting); +} + +TString TPoolSettings::TExtractor::operator()(TDuration* setting) const { + return ToString(setting->Seconds()); +} + +//// TPoolSettings + +TPoolSettings::TPoolSettings(const google::protobuf::Map& properties) { + for (auto& [property, value] : GetPropertiesMap()) { + if (auto propertyIt = properties.find(property); propertyIt != properties.end()) { + std::visit(TPoolSettings::TParser{propertyIt->second}, value); + } + } +} + +std::unordered_map TPoolSettings::GetPropertiesMap(bool restricted) { std::unordered_map properties = { - {"concurrent_query_limit", &settings.ConcurrentQueryLimit}, - {"queue_size", &settings.QueueSize}, - {"query_memory_limit_percent_per_node", &settings.QueryMemoryLimitPercentPerNode}, - {"database_load_cpu_threshold", &settings.DatabaseLoadCpuThreshold} + {"concurrent_query_limit", &ConcurrentQueryLimit}, + {"queue_size", &QueueSize}, + {"query_memory_limit_percent_per_node", &QueryMemoryLimitPercentPerNode}, + {"database_load_cpu_threshold", &DatabaseLoadCpuThreshold} }; if (!restricted) { - properties.insert({"query_cancel_after_seconds", &settings.QueryCancelAfter}); + properties.insert({"query_cancel_after_seconds", &QueryCancelAfter}); } return properties; } +void TPoolSettings::Validate() const { + if (ConcurrentQueryLimit > POOL_MAX_CONCURRENT_QUERY_LIMIT) { + throw yexception() << "Invalid resource pool configuration, concurrent_query_limit is " << ConcurrentQueryLimit << ", that exceeds limit in " << POOL_MAX_CONCURRENT_QUERY_LIMIT; + } + if (QueueSize != -1 && ConcurrentQueryLimit == -1 && DatabaseLoadCpuThreshold < 0.0) { + throw yexception() << "Invalid resource pool configuration, queue_size unsupported without concurrent_query_limit or database_load_cpu_threshold"; + } +} + } // namespace NKikimr::NResourcePool diff --git a/ydb/core/resource_pools/resource_pool_settings.h b/ydb/core/resource_pools/resource_pool_settings.h index cecfd4eefb59..4ad3f451a609 100644 --- a/ydb/core/resource_pools/resource_pool_settings.h +++ b/ydb/core/resource_pools/resource_pool_settings.h @@ -1,66 +1,49 @@ #pragma once +#include "settings_common.h" + +#include + #include -#include namespace NKikimr::NResourcePool { inline constexpr char DEFAULT_POOL_ID[] = "default"; -typedef double TPercent; +inline constexpr i64 POOL_MAX_CONCURRENT_QUERY_LIMIT = 1000; -struct TPoolSettings { - i32 ConcurrentQueryLimit = -1; // -1 = disabled - i32 QueueSize = -1; // -1 = disabled - TDuration QueryCancelAfter = TDuration::Zero(); // 0 = disabled +struct TPoolSettings : public TSettingsBase { + typedef double TPercent; - TPercent QueryMemoryLimitPercentPerNode = -1; // Percent from node memory capacity, -1 = disabled + using TBase = TSettingsBase; + using TProperty = std::variant; - TPercent DatabaseLoadCpuThreshold = -1; // -1 = disabled + struct TParser : public TBase::TParser { + void operator()(i32* setting) const; + void operator()(TDuration* setting) const; + void operator()(TPercent* setting) const; + }; - bool operator==(const TPoolSettings& other) const = default; -}; + struct TExtractor : public TBase::TExtractor { + TString operator()(i32* setting) const; + TString operator()(double* setting) const; + TString operator()(TDuration* setting) const; + }; -struct TSettingsParser { - const TString& value; - - void operator()(i32* setting) const { - *setting = FromString(value); - if (*setting < -1) { - throw yexception() << "Invalid integer value " << *setting << ", it is should be greater or equal -1"; - } - } - - void operator()(TDuration* setting) const { - ui64 seconds = FromString(value); - if (seconds > std::numeric_limits::max() / 1000) { - throw yexception() << "Invalid seconds value " << seconds << ", it is should be less or equal than " << std::numeric_limits::max() / 1000; - } - *setting = TDuration::Seconds(seconds); - } - - void operator()(TPercent* setting) const { - *setting = FromString(value); - if (*setting != -1 && (*setting < 0 || 100 < *setting)) { - throw yexception() << "Invalid percent value " << *setting << ", it is should be between 0 and 100 or -1"; - } - } -}; + TPoolSettings() = default; + TPoolSettings(const google::protobuf::Map& properties); -struct TSettingsExtractor { - template - TString operator()(T* setting) const { - return ToString(*setting); - } + bool operator==(const TPoolSettings& other) const = default; - template <> - TString operator()(TDuration* setting) const { - return ToString(setting->Seconds()); - } -}; + std::unordered_map GetPropertiesMap(bool restricted = false); + void Validate() const; -using TProperty = std::variant; -std::unordered_map GetPropertiesMap(TPoolSettings& settings, bool restricted = false); + i32 ConcurrentQueryLimit = -1; // -1 = disabled + i32 QueueSize = -1; // -1 = disabled + TDuration QueryCancelAfter = TDuration::Zero(); // 0 = disabled + TPercent QueryMemoryLimitPercentPerNode = -1; // Percent from node memory capacity, -1 = disabled + TPercent DatabaseLoadCpuThreshold = -1; // -1 = disabled +}; } // namespace NKikimr::NResourcePool diff --git a/ydb/core/resource_pools/resource_pool_settings_ut.cpp b/ydb/core/resource_pools/resource_pool_settings_ut.cpp index 2e4b2058bcf1..9b67b03afa4a 100644 --- a/ydb/core/resource_pools/resource_pool_settings_ut.cpp +++ b/ydb/core/resource_pools/resource_pool_settings_ut.cpp @@ -11,52 +11,52 @@ using namespace NResourcePool; Y_UNIT_TEST_SUITE(ResourcePoolTest) { Y_UNIT_TEST(IntSettingsParsing) { TPoolSettings settings; - auto propertiesMap = GetPropertiesMap(settings); + auto propertiesMap = settings.GetPropertiesMap(); - std::visit(TSettingsParser{"-1"}, propertiesMap["queue_size"]); + std::visit(TPoolSettings::TParser{"-1"}, propertiesMap["queue_size"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueueSize, -1); - std::visit(TSettingsParser{"10"}, propertiesMap["queue_size"]); + std::visit(TPoolSettings::TParser{"10"}, propertiesMap["queue_size"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueueSize, 10); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"string_value"}, propertiesMap["queue_size"]), TFromStringException, "Unexpected symbol \"s\" at pos 0 in string \"string_value\"."); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"2147483648"}, propertiesMap["queue_size"]), TFromStringException, "Integer overflow in string \"2147483648\"."); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"-2"}, propertiesMap["queue_size"]), yexception, "Invalid integer value -2, it is should be greater or equal -1"); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"string_value"}, propertiesMap["queue_size"]), TFromStringException, "Unexpected symbol \"s\" at pos 0 in string \"string_value\"."); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"2147483648"}, propertiesMap["queue_size"]), TFromStringException, "Integer overflow in string \"2147483648\"."); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"-2"}, propertiesMap["queue_size"]), yexception, "Invalid integer value -2, it is should be greater or equal -1"); } Y_UNIT_TEST(SecondsSettingsParsing) { TPoolSettings settings; - auto propertiesMap = GetPropertiesMap(settings); + auto propertiesMap = settings.GetPropertiesMap(); - std::visit(TSettingsParser{"0"}, propertiesMap["query_cancel_after_seconds"]); + std::visit(TPoolSettings::TParser{"0"}, propertiesMap["query_cancel_after_seconds"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryCancelAfter, TDuration::Zero()); - std::visit(TSettingsParser{"10"}, propertiesMap["query_cancel_after_seconds"]); + std::visit(TPoolSettings::TParser{"10"}, propertiesMap["query_cancel_after_seconds"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryCancelAfter, TDuration::Seconds(10)); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"-1"}, propertiesMap["query_cancel_after_seconds"]), TFromStringException, "Unexpected symbol \"-\" at pos 0 in string \"-1\"."); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"18446744073709552"}, propertiesMap["query_cancel_after_seconds"]), yexception, "Invalid seconds value 18446744073709552, it is should be less or equal than 18446744073709551"); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"-1"}, propertiesMap["query_cancel_after_seconds"]), TFromStringException, "Unexpected symbol \"-\" at pos 0 in string \"-1\"."); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"18446744073709552"}, propertiesMap["query_cancel_after_seconds"]), yexception, "Invalid seconds value 18446744073709552, it is should be less or equal than 18446744073709551"); } Y_UNIT_TEST(PercentSettingsParsing) { TPoolSettings settings; - auto propertiesMap = GetPropertiesMap(settings); + auto propertiesMap = settings.GetPropertiesMap(); - std::visit(TSettingsParser{"-1"}, propertiesMap["query_memory_limit_percent_per_node"]); + std::visit(TPoolSettings::TParser{"-1"}, propertiesMap["query_memory_limit_percent_per_node"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryMemoryLimitPercentPerNode, -1); - std::visit(TSettingsParser{"0"}, propertiesMap["query_memory_limit_percent_per_node"]); + std::visit(TPoolSettings::TParser{"0"}, propertiesMap["query_memory_limit_percent_per_node"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryMemoryLimitPercentPerNode, 0); - std::visit(TSettingsParser{"55.5"}, propertiesMap["query_memory_limit_percent_per_node"]); + std::visit(TPoolSettings::TParser{"55.5"}, propertiesMap["query_memory_limit_percent_per_node"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryMemoryLimitPercentPerNode, 55.5); - std::visit(TSettingsParser{"100"}, propertiesMap["query_memory_limit_percent_per_node"]); + std::visit(TPoolSettings::TParser{"100"}, propertiesMap["query_memory_limit_percent_per_node"]); UNIT_ASSERT_VALUES_EQUAL(settings.QueryMemoryLimitPercentPerNode, 100); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"-1.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value -1.5, it is should be between 0 and 100 or -1"); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"-0.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value -0.5, it is should be between 0 and 100 or -1"); - UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TSettingsParser{"101.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value 101.5, it is should be between 0 and 100 or -1"); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"-1.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value -1.5, it is should be between 0 and 100 or -1"); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"-0.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value -0.5, it is should be between 0 and 100 or -1"); + UNIT_ASSERT_EXCEPTION_CONTAINS(std::visit(TPoolSettings::TParser{"101.5"}, propertiesMap["query_memory_limit_percent_per_node"]), yexception, "Invalid percent value 101.5, it is should be between 0 and 100 or -1"); } Y_UNIT_TEST(SettingsExtracting) { @@ -65,14 +65,29 @@ Y_UNIT_TEST_SUITE(ResourcePoolTest) { settings.QueueSize = -1; settings.QueryCancelAfter = TDuration::Seconds(15); settings.QueryMemoryLimitPercentPerNode = 0.5; - auto propertiesMap = GetPropertiesMap(settings); + auto propertiesMap = settings.GetPropertiesMap(); - TSettingsExtractor extractor; + TPoolSettings::TExtractor extractor; UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["concurrent_query_limit"]), "10"); UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["queue_size"]), "-1"); UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["query_cancel_after_seconds"]), "15"); UNIT_ASSERT_VALUES_EQUAL(std::visit(extractor, propertiesMap["query_memory_limit_percent_per_node"]), "0.5"); } + + Y_UNIT_TEST(SettingsValidation) { + { // Max concurrent query limit validation + TPoolSettings settings; + settings.ConcurrentQueryLimit = POOL_MAX_CONCURRENT_QUERY_LIMIT + 1; + UNIT_ASSERT_EXCEPTION_CONTAINS(settings.Validate(), yexception, TStringBuilder() << "Invalid resource pool configuration, concurrent_query_limit is " << settings.ConcurrentQueryLimit << ", that exceeds limit in " << POOL_MAX_CONCURRENT_QUERY_LIMIT); + } + + { // Unused queue size validation + + TPoolSettings settings; + settings.QueueSize = 1; + UNIT_ASSERT_EXCEPTION_CONTAINS(settings.Validate(), yexception, TStringBuilder() << "Invalid resource pool configuration, queue_size unsupported without concurrent_query_limit or database_load_cpu_threshold"); + } + } } } // namespace NKikimr diff --git a/ydb/core/resource_pools/settings_common.h b/ydb/core/resource_pools/settings_common.h new file mode 100644 index 000000000000..e1f1ef486d38 --- /dev/null +++ b/ydb/core/resource_pools/settings_common.h @@ -0,0 +1,19 @@ +#pragma once + +#include + + +namespace NKikimr::NResourcePool { + +struct TSettingsBase { + struct TParser { + const TString& Value; + }; + + struct TExtractor { + }; + + bool operator==(const TSettingsBase& other) const = default; +}; + +} // namespace NKikimr::NResourcePool diff --git a/ydb/core/resource_pools/ut/ya.make b/ydb/core/resource_pools/ut/ya.make index 035ff36c85c8..9a95820a6887 100644 --- a/ydb/core/resource_pools/ut/ya.make +++ b/ydb/core/resource_pools/ut/ya.make @@ -7,6 +7,7 @@ PEERDIR( ) SRCS( + resource_pool_classifier_settings_ut.cpp resource_pool_settings_ut.cpp ) diff --git a/ydb/core/resource_pools/ya.make b/ydb/core/resource_pools/ya.make index 831bf5e53fb1..5fd79c0bf4e4 100644 --- a/ydb/core/resource_pools/ya.make +++ b/ydb/core/resource_pools/ya.make @@ -1,11 +1,14 @@ LIBRARY() SRCS( + resource_pool_classifier_settings.cpp resource_pool_settings.cpp ) PEERDIR( + contrib/libs/protobuf util + ydb/library/aclib ) END() diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_alter_resource_pool.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_alter_resource_pool.cpp index 25ce9ad0c08e..011291768d6d 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_alter_resource_pool.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_alter_resource_pool.cpp @@ -149,6 +149,7 @@ class TAlterResourcePool : public TSubOperation { Y_ABORT_UNLESS(oldResourcePoolInfo); const TResourcePoolInfo::TPtr resourcePoolInfo = NResourcePool::ModifyResourcePool(resourcePoolDescription, oldResourcePoolInfo); Y_ABORT_UNLESS(resourcePoolInfo); + RETURN_RESULT_UNLESS(NResourcePool::IsResourcePoolInfoValid(result, resourcePoolInfo)); result->SetPathId(dstPath.Base()->PathId.LocalPathId); const TPathElement::TPtr resourcePool = ReplaceResourcePoolPathElement(dstPath); diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.cpp index a7d86f9a9035..5179d835d472 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.cpp @@ -1,6 +1,8 @@ #include "schemeshard__operation_common_resource_pool.h" #include "schemeshard_impl.h" +#include + namespace NKikimr::NSchemeShard::NResourcePool { @@ -34,7 +36,7 @@ TPath::TChecker IsParentPathValid(const TPath& parentPath) { } bool IsParentPathValid(const THolder& result, const TPath& parentPath) { - const TString& resourcePoolsDir = JoinPath({parentPath.GetDomainPathString(), ".resource_pools"}); + const TString& resourcePoolsDir = JoinPath({parentPath.GetDomainPathString(), ".metadata/workload_manager/pools"}); if (parentPath.PathString() != resourcePoolsDir) { result->SetError(NKikimrScheme::EStatus::StatusSchemeError, TStringBuilder() << "Resource pools shoud be placed in " << resourcePoolsDir); return false; @@ -90,6 +92,17 @@ bool IsDescriptionValid(const THolder& result, const NKikimrSc return true; } +bool IsResourcePoolInfoValid(const THolder& result, const TResourcePoolInfo::TPtr& info) { + try { + NKikimr::NResourcePool::TPoolSettings settings(info->Properties.GetProperties()); + settings.Validate(); + } catch (...) { + result->SetError(NKikimrScheme::StatusSchemeError, CurrentExceptionMessage()); + return false; + } + return true; +} + TTxState& CreateTransaction(const TOperationId& operationId, const TOperationContext& context, const TPathId& resourcePoolPathId, TTxState::ETxType txType) { Y_ABORT_UNLESS(!context.SS->FindTx(operationId)); TTxState& txState = context.SS->CreateTx(operationId, txType, resourcePoolPathId); diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.h b/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.h index 94909926290f..c784de0c7c63 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.h +++ b/ydb/core/tx/schemeshard/schemeshard__operation_common_resource_pool.h @@ -24,6 +24,8 @@ bool IsApplyIfChecksPassed(const TTxTransaction& transaction, const THolder& result, const NKikimrSchemeOp::TResourcePoolDescription& description); +bool IsResourcePoolInfoValid(const THolder& result, const TResourcePoolInfo::TPtr& info); + TTxState& CreateTransaction(const TOperationId& operationId, const TOperationContext& context, const TPathId& resourcePoolPathId, TTxState::ETxType txType); void RegisterParentPathDependencies(const TOperationId& operationId, const TOperationContext& context, const TPath& parentPath); diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_create_resource_pool.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_create_resource_pool.cpp index a765216aa5b5..fba996674b60 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_create_resource_pool.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_create_resource_pool.cpp @@ -173,6 +173,7 @@ class TCreateResourcePool : public TSubOperation { const TResourcePoolInfo::TPtr resourcePoolInfo = NResourcePool::CreateResourcePool(resourcePoolDescription, 1); Y_ABORT_UNLESS(resourcePoolInfo); + RETURN_RESULT_UNLESS(NResourcePool::IsResourcePoolInfoValid(result, resourcePoolInfo)); AddPathInSchemeShard(result, dstPath, owner); const TPathElement::TPtr resourcePool = CreateResourcePoolPathElement(dstPath); diff --git a/ydb/core/tx/schemeshard/ut_resource_pool/ut_resource_pool.cpp b/ydb/core/tx/schemeshard/ut_resource_pool/ut_resource_pool.cpp index f5a7f8754a5a..3d27ec3cd587 100644 --- a/ydb/core/tx/schemeshard/ut_resource_pool/ut_resource_pool.cpp +++ b/ydb/core/tx/schemeshard/ut_resource_pool/ut_resource_pool.cpp @@ -9,16 +9,16 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" )", {NKikimrScheme::StatusAccepted}); env.TestWaitNotification(runtime, txId); - TestLs(runtime, "/MyRoot/.resource_pools/MyResourcePool", false, NLs::PathExist); + TestLs(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool", false, NLs::PathExist); } Y_UNIT_TEST(CreateResourcePoolWithProperties) { @@ -26,10 +26,10 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" Properties { Properties { @@ -49,7 +49,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { properties.MutableProperties()->insert({"concurrent_query_limit", "10"}); properties.MutableProperties()->insert({"query_cancel_after_seconds", "60"}); - auto describeResult = DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"); + auto describeResult = DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"); TestDescribeResult(describeResult, {NLs::PathExist}); UNIT_ASSERT(describeResult.GetPathDescription().HasResourcePoolDescription()); const auto& resourcePoolDescription = describeResult.GetPathDescription().GetResourcePoolDescription(); @@ -63,21 +63,21 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" )", {NKikimrScheme::StatusAccepted}); env.TestWaitNotification(runtime, txId); - TestLs(runtime, "/MyRoot/.resource_pools/MyResourcePool", false, NLs::PathExist); + TestLs(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool", false, NLs::PathExist); - TestDropResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", "MyResourcePool"); + TestDropResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", "MyResourcePool"); env.TestWaitNotification(runtime, txId); - TestLs(runtime, "/MyRoot/.resource_pools/MyResourcePool", false, NLs::PathNotExist); + TestLs(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool", false, NLs::PathNotExist); } Y_UNIT_TEST(DropResourcePoolTwice) { @@ -85,16 +85,16 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" )"); env.TestWaitNotification(runtime, txId); - AsyncDropResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", "MyResourcePool"); - AsyncDropResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", "MyResourcePool"); + AsyncDropResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", "MyResourcePool"); + AsyncDropResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", "MyResourcePool"); TestModificationResult(runtime, txId - 1); auto ev = runtime.GrabEdgeEvent(); @@ -106,7 +106,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { UNIT_ASSERT_VALUES_EQUAL(record.GetPathDropTxId(), txId - 1); env.TestWaitNotification(runtime, txId - 1); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"), { + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"), { NLs::PathNotExist }); } @@ -116,11 +116,11 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 123; - AsyncMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + AsyncMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool1" )"); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool2" )"); TestModificationResult(runtime, txId-2, NKikimrScheme::StatusAccepted); @@ -129,14 +129,14 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { env.TestWaitNotification(runtime, {txId, txId-1, txId-2}); - TestDescribe(runtime, "/MyRoot/.resource_pools/MyResourcePool1"); - TestDescribe(runtime, "/MyRoot/.resource_pools/MyResourcePool2"); + TestDescribe(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool1"); + TestDescribe(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool2"); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools"), {NLs::PathVersionEqual(7)}); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool1"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool1"), {NLs::PathVersionEqual(2)}); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool2"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool2"), {NLs::PathVersionEqual(2)}); } @@ -151,12 +151,12 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { Name: "NilNoviSubLuna" )"; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", resourcePoolConfig); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", resourcePoolConfig); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", resourcePoolConfig); + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", resourcePoolConfig); + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", resourcePoolConfig); + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", resourcePoolConfig); ui64 sts[3]; sts[0] = TestModificationResults(runtime, txId-2, {ESts::StatusAccepted, ESts::StatusMultipleModifications, ESts::StatusAlreadyExists}); @@ -165,13 +165,13 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { for (ui32 i=0; i<3; ++i) { if (sts[i] == ESts::StatusAlreadyExists) { - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/NilNoviSubLuna"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/NilNoviSubLuna"), {NLs::Finished, NLs::IsResourcePool}); } if (sts[i] == ESts::StatusMultipleModifications) { - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/NilNoviSubLuna"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/NilNoviSubLuna"), {NLs::Finished, NLs::IsResourcePool}); } @@ -179,12 +179,12 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { env.TestWaitNotification(runtime, {txId-2, txId-1, txId}); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/NilNoviSubLuna"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/NilNoviSubLuna"), {NLs::Finished, NLs::IsResourcePool, NLs::PathVersionEqual(2)}); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", resourcePoolConfig, {ESts::StatusAlreadyExists}); + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", resourcePoolConfig, {ESts::StatusAlreadyExists}); } Y_UNIT_TEST(ReadOnlyMode) { @@ -192,11 +192,11 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 123; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); AsyncMkDir(runtime, ++txId, "/MyRoot", "SubDirA"); - AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + AsyncCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" )"); @@ -211,13 +211,13 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { // Check that describe works TestDescribeResult(DescribePath(runtime, "/MyRoot/SubDirA"), {NLs::Finished}); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"), {NLs::Finished, NLs::IsResourcePool}); // Check that new modifications fail TestMkDir(runtime, ++txId, "/MyRoot", "SubDirBBBB", {NKikimrScheme::StatusReadOnly}); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool2" )", {NKikimrScheme::StatusReadOnly}); @@ -235,18 +235,18 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 123; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); TestCreateResourcePool(runtime, ++txId, "/MyRoot", R"( Name: "AnotherDir/MyResourcePool" - )", {{NKikimrScheme::StatusSchemeError, "Resource pools shoud be placed in /MyRoot/.resource_pools"}}); + )", {{NKikimrScheme::StatusSchemeError, "Resource pools shoud be placed in /MyRoot/.metadata/workload_manager/pools"}}); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "AnotherDir/MyResourcePool" - )", {{NKikimrScheme::StatusSchemeError, "Resource pools shoud be placed in /MyRoot/.resource_pools"}}); + )", {{NKikimrScheme::StatusSchemeError, "Resource pools shoud be placed in /MyRoot/.metadata/workload_manager/pools"}}); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "" )", {{NKikimrScheme::StatusSchemeError, "error: path part shouldn't be empty"}}); } @@ -256,10 +256,10 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestCreateResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" Properties { Properties { @@ -280,7 +280,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { properties.MutableProperties()->insert({"query_count_limit", "50"}); { - auto describeResult = DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"); + auto describeResult = DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"); TestDescribeResult(describeResult, {NLs::PathExist}); UNIT_ASSERT(describeResult.GetPathDescription().HasResourcePoolDescription()); const auto& resourcePoolDescription = describeResult.GetPathDescription().GetResourcePoolDescription(); @@ -289,7 +289,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { UNIT_ASSERT_VALUES_EQUAL(resourcePoolDescription.GetProperties().DebugString(), properties.DebugString()); } - TestAlterResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestAlterResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" Properties { Properties { @@ -309,7 +309,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { properties.MutableProperties()->insert({"query_cancel_after_seconds", "60"}); { - auto describeResult = DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"); + auto describeResult = DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"); TestDescribeResult(describeResult, {NLs::PathExist}); UNIT_ASSERT(describeResult.GetPathDescription().HasResourcePoolDescription()); const auto& resourcePoolDescription = describeResult.GetPathDescription().GetResourcePoolDescription(); @@ -324,10 +324,10 @@ Y_UNIT_TEST_SUITE(TResourcePoolTest) { TTestEnv env(runtime); ui64 txId = 100; - TestMkDir(runtime, ++txId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++txId, "/MyRoot", ".metadata/workload_manager/pools"); env.TestWaitNotification(runtime, txId); - TestAlterResourcePool(runtime, ++txId, "/MyRoot/.resource_pools", R"( + TestAlterResourcePool(runtime, ++txId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" Properties { Properties { diff --git a/ydb/core/tx/schemeshard/ut_resource_pool_reboots/ut_resource_pool_reboots.cpp b/ydb/core/tx/schemeshard/ut_resource_pool_reboots/ut_resource_pool_reboots.cpp index d8b772e24bdc..3bc4ae28cda5 100644 --- a/ydb/core/tx/schemeshard/ut_resource_pool_reboots/ut_resource_pool_reboots.cpp +++ b/ydb/core/tx/schemeshard/ut_resource_pool_reboots/ut_resource_pool_reboots.cpp @@ -7,9 +7,9 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { Y_UNIT_TEST(CreateResourcePoolWithReboots) { TTestWithReboots t; t.Run([&](TTestActorRuntime& runtime, bool& activeZone) { - AsyncMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + AsyncMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); - AsyncCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + AsyncCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "MyResourcePool" Properties { Properties { @@ -31,7 +31,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - auto describeResult = DescribePath(runtime, "/MyRoot/.resource_pools/MyResourcePool"); + auto describeResult = DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/MyResourcePool"); TestDescribeResult(describeResult, {NLs::Finished}); UNIT_ASSERT(describeResult.GetPathDescription().HasResourcePoolDescription()); @@ -46,22 +46,22 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { Y_UNIT_TEST(ParallelCreateDrop) { TTestWithReboots t; t.Run([&](TTestActorRuntime& runtime, bool& activeZone) { - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - AsyncCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + AsyncCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "DropMe" )"); - AsyncDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "DropMe"); + AsyncDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "DropMe"); t.TestEnv->TestWaitNotification(runtime, t.TxId-1); - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "DropMe"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "DropMe"); t.TestEnv->TestWaitNotification(runtime, t.TxId); { TInactiveZone inactive(activeZone); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/DropMe"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/DropMe"), {NLs::PathNotExist}); } }); @@ -71,22 +71,22 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { TTestWithReboots t; t.Run([&](TTestActorRuntime& runtime, bool& activeZone) { { - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); TInactiveZone inactive(activeZone); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); { TInactiveZone inactive(activeZone); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/ResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/ResourcePool"), {NLs::PathNotExist}); } }); @@ -98,21 +98,21 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); { TInactiveZone inactive(activeZone); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/ResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/ResourcePool"), {NLs::PathNotExist}); } }); @@ -124,32 +124,32 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestCreateResourcePool(runtime, t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); { TInactiveZone inactive(activeZone); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/ResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/ResourcePool"), {NLs::PathNotExist}); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/ResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/ResourcePool"), {NLs::PathNotExist}); } }); @@ -161,19 +161,19 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); @@ -181,7 +181,7 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } }); @@ -193,29 +193,29 @@ Y_UNIT_TEST_SUITE(TResourcePoolTestReboots) { { TInactiveZone inactive(activeZone); - TestMkDir(runtime, ++t.TxId, "/MyRoot", ".resource_pools"); + TestMkDir(runtime, ++t.TxId, "/MyRoot", ".metadata/workload_manager/pools"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); - TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", R"( + TestCreateResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", R"( Name: "ResourcePool" )"); t.TestEnv->TestWaitNotification(runtime, t.TxId); } - TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.resource_pools", "ResourcePool"); + TestDropResourcePool(runtime, ++t.TxId, "/MyRoot/.metadata/workload_manager/pools", "ResourcePool"); t.TestEnv->TestWaitNotification(runtime, t.TxId); { TInactiveZone inactive(activeZone); - TestDescribeResult(DescribePath(runtime, "/MyRoot/.resource_pools/ResourcePool"), + TestDescribeResult(DescribePath(runtime, "/MyRoot/.metadata/workload_manager/pools/ResourcePool"), {NLs::PathNotExist}); } }); diff --git a/ydb/core/tx/schemeshard/ya.make b/ydb/core/tx/schemeshard/ya.make index dad02ce621bc..e17c0a11b792 100644 --- a/ydb/core/tx/schemeshard/ya.make +++ b/ydb/core/tx/schemeshard/ya.make @@ -262,6 +262,7 @@ PEERDIR( ydb/core/persqueue/events ydb/core/persqueue/writer ydb/core/protos + ydb/core/resource_pools ydb/core/scheme ydb/core/statistics ydb/core/sys_view/partition_stats diff --git a/ydb/core/tx/tiering/rule/manager.cpp b/ydb/core/tx/tiering/rule/manager.cpp index c6ea9e9f6130..99f8ad1177a2 100644 --- a/ydb/core/tx/tiering/rule/manager.cpp +++ b/ydb/core/tx/tiering/rule/manager.cpp @@ -6,7 +6,7 @@ namespace NKikimr::NColumnShard::NTiers { void TTieringRulesManager::DoPrepareObjectsBeforeModification(std::vector&& objects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const { + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { TActivationContext::Register(new TRulePreparationActor(std::move(objects), controller, context)); } diff --git a/ydb/core/tx/tiering/rule/manager.h b/ydb/core/tx/tiering/rule/manager.h index 3268c90021c4..d5646dbf3002 100644 --- a/ydb/core/tx/tiering/rule/manager.h +++ b/ydb/core/tx/tiering/rule/manager.h @@ -9,7 +9,7 @@ class TTieringRulesManager: public NMetadata::NModifications::TGenericOperations protected: virtual void DoPrepareObjectsBeforeModification(std::vector&& objects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NMetadata::NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; diff --git a/ydb/core/tx/tiering/tier/manager.cpp b/ydb/core/tx/tiering/tier/manager.cpp index a64d2a7603ab..b64439cf62d4 100644 --- a/ydb/core/tx/tiering/tier/manager.cpp +++ b/ydb/core/tx/tiering/tier/manager.cpp @@ -65,7 +65,7 @@ NMetadata::NModifications::TOperationParsingResult TTiersManager::DoBuildPatchFr void TTiersManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { TActivationContext::Register(new TTierPreparationActor(std::move(patchedObjects), controller, context)); } diff --git a/ydb/core/tx/tiering/tier/manager.h b/ydb/core/tx/tiering/tier/manager.h index ba777648139c..7d8626c8c36c 100644 --- a/ydb/core/tx/tiering/tier/manager.h +++ b/ydb/core/tx/tiering/tier/manager.h @@ -9,7 +9,7 @@ class TTiersManager: public NMetadata::NModifications::TGenericOperationsManager protected: virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NMetadata::NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; diff --git a/ydb/library/yql/sql/v1/SQLv1.g.in b/ydb/library/yql/sql/v1/SQLv1.g.in index a71ee4275864..e26288359aa2 100644 --- a/ydb/library/yql/sql/v1/SQLv1.g.in +++ b/ydb/library/yql/sql/v1/SQLv1.g.in @@ -66,6 +66,9 @@ sql_stmt_core: | create_resource_pool_stmt | alter_resource_pool_stmt | drop_resource_pool_stmt + | create_resource_pool_classifier_stmt + | alter_resource_pool_classifier_stmt + | drop_resource_pool_classifier_stmt ; expr: @@ -809,13 +812,26 @@ alter_resource_pool_stmt: ALTER RESOURCE POOL object_ref alter_resource_pool_action (COMMA alter_resource_pool_action)* ; alter_resource_pool_action: - alter_table_set_table_setting_uncompat - | alter_table_set_table_setting_compat + alter_table_set_table_setting_compat | alter_table_reset_table_setting ; drop_resource_pool_stmt: DROP RESOURCE POOL object_ref; +create_resource_pool_classifier_stmt: CREATE RESOURCE POOL CLASSIFIER object_ref + with_table_settings +; + +alter_resource_pool_classifier_stmt: ALTER RESOURCE POOL CLASSIFIER object_ref + alter_resource_pool_classifier_action (COMMA alter_resource_pool_classifier_action)* +; +alter_resource_pool_classifier_action: + alter_table_set_table_setting_compat + | alter_table_reset_table_setting +; + +drop_resource_pool_classifier_stmt: DROP RESOURCE POOL CLASSIFIER object_ref; + create_replication_stmt: CREATE ASYNC REPLICATION object_ref FOR replication_target (COMMA replication_target)* WITH LPAREN replication_settings RPAREN @@ -1200,6 +1216,7 @@ keyword_as_compat: | CASCADE | CHANGEFEED | CHECK + | CLASSIFIER // | COLLATE | COMMIT | CONDITIONAL @@ -1412,6 +1429,7 @@ keyword_compat: ( | CASCADE | CHANGEFEED | CHECK + | CLASSIFIER | COLLATE | COMMIT | CONDITIONAL @@ -1728,6 +1746,7 @@ CASE: C A S E; CAST: C A S T; CHANGEFEED: C H A N G E F E E D; CHECK: C H E C K; +CLASSIFIER: C L A S S I F I E R; COLLATE: C O L L A T E; COLUMN: C O L U M N; COLUMNS: C O L U M N S; diff --git a/ydb/library/yql/sql/v1/format/sql_format.cpp b/ydb/library/yql/sql/v1/format/sql_format.cpp index 22de14a4da74..2efb25fdb07a 100644 --- a/ydb/library/yql/sql/v1/format/sql_format.cpp +++ b/ydb/library/yql/sql/v1/format/sql_format.cpp @@ -1529,6 +1529,39 @@ friend struct TStaticData; VisitAllFields(TRule_drop_resource_pool_stmt::GetDescriptor(), msg); } + void VisitCreateResourcePoolClassifier(const TRule_create_resource_pool_classifier_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitAllFields(TRule_create_resource_pool_classifier_stmt::GetDescriptor(), msg); + } + + void VisitAlterResourcePoolClassifier(const TRule_alter_resource_pool_classifier_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitToken(msg.GetToken1()); + VisitToken(msg.GetToken2()); + VisitToken(msg.GetToken3()); + VisitToken(msg.GetToken4()); + Visit(msg.GetRule_object_ref5()); + + NewLine(); + PushCurrentIndent(); + Visit(msg.GetRule_alter_resource_pool_classifier_action6()); + for (const auto& action : msg.GetBlock7()) { + Visit(action.GetToken1()); // comma + NewLine(); + Visit(action.GetRule_alter_resource_pool_classifier_action2()); + } + + PopCurrentIndent(); + } + + void VisitDropResourcePoolClassifier(const TRule_drop_resource_pool_classifier_stmt& msg) { + PosFromToken(msg.GetToken1()); + NewLine(); + VisitAllFields(TRule_drop_resource_pool_classifier_stmt::GetDescriptor(), msg); + } + void VisitAllFields(const NProtoBuf::Descriptor* descr, const NProtoBuf::Message& msg) { VisitAllFieldsImpl(this, descr, msg); } @@ -2745,7 +2778,10 @@ TStaticData::TStaticData() {TRule_drop_view_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropView)}, {TRule_create_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCreateResourcePool)}, {TRule_alter_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitAlterResourcePool)}, - {TRule_drop_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropResourcePool)} + {TRule_drop_resource_pool_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropResourcePool)}, + {TRule_create_resource_pool_classifier_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCreateResourcePoolClassifier)}, + {TRule_alter_resource_pool_classifier_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitAlterResourcePoolClassifier)}, + {TRule_drop_resource_pool_classifier_stmt::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitDropResourcePoolClassifier)} }) , ObfuscatingVisitDispatch({ {TToken::GetDescriptor(), MakeObfuscatingFunctor(&TObfuscatingVisitor::VisitToken)}, diff --git a/ydb/library/yql/sql/v1/format/sql_format_ut.cpp b/ydb/library/yql/sql/v1/format/sql_format_ut.cpp index e18a3f3cf80b..97ce7eccdd88 100644 --- a/ydb/library/yql/sql/v1/format/sql_format_ut.cpp +++ b/ydb/library/yql/sql/v1/format/sql_format_ut.cpp @@ -1604,8 +1604,8 @@ FROM Input MATCH_RECOGNIZE (PATTERN (A) DEFINE A AS A); "CREATE RESOURCE POOL naMe WITH (a = \"b\");\n"}, {"create resource pool eds with (a=\"a\",b=\"b\",c = true)", "CREATE RESOURCE POOL eds WITH (\n\ta = \"a\",\n\tb = \"b\",\n\tc = TRUE\n);\n"}, - {"alTer reSOurcE poOl naMe sEt a tRue, resEt (b, c), seT (x=y, z=false)", - "ALTER RESOURCE POOL naMe\n\tSET a TRUE,\n\tRESET (b, c),\n\tSET (x = y, z = FALSE);\n"}, + {"alTer reSOurcE poOl naMe resEt (b, c), seT (x=y, z=false)", + "ALTER RESOURCE POOL naMe\n\tRESET (b, c),\n\tSET (x = y, z = FALSE);\n"}, {"alter resource pool eds reset (a), set (x=y)", "ALTER RESOURCE POOL eds\n\tRESET (a),\n\tSET (x = y);\n"}, {"dRop reSourCe poOl naMe", @@ -1615,4 +1615,22 @@ FROM Input MATCH_RECOGNIZE (PATTERN (A) DEFINE A AS A); TSetup setup; setup.Run(cases); } + + Y_UNIT_TEST(ResourcePoolClassifierOperations) { + TCases cases = { + {"creAte reSourCe poOl ClaSsiFIer naMe With (a = \"b\")", + "CREATE RESOURCE POOL CLASSIFIER naMe WITH (a = \"b\");\n"}, + {"create resource pool classifier eds with (a=\"a\",b=\"b\",c = true)", + "CREATE RESOURCE POOL CLASSIFIER eds WITH (\n\ta = \"a\",\n\tb = \"b\",\n\tc = TRUE\n);\n"}, + {"alTer reSOurcE poOl ClaSsiFIer naMe resEt (b, c), seT (x=y, z=false)", + "ALTER RESOURCE POOL CLASSIFIER naMe\n\tRESET (b, c),\n\tSET (x = y, z = FALSE);\n"}, + {"alter resource pool classifier eds reset (a), set (x=y)", + "ALTER RESOURCE POOL CLASSIFIER eds\n\tRESET (a),\n\tSET (x = y);\n"}, + {"dRop reSourCe poOl ClaSsiFIer naMe", + "DROP RESOURCE POOL CLASSIFIER naMe;\n"}, + }; + + TSetup setup; + setup.Run(cases); + } } diff --git a/ydb/library/yql/sql/v1/sql.cpp b/ydb/library/yql/sql/v1/sql.cpp index e50c776269e5..56b6e202cc35 100644 --- a/ydb/library/yql/sql/v1/sql.cpp +++ b/ydb/library/yql/sql/v1/sql.cpp @@ -167,6 +167,9 @@ bool NeedUseForAllStatements(const TRule_sql_stmt_core::AltCase& subquery) { case TRule_sql_stmt_core::kAltSqlStmtCore45: // create resource pool case TRule_sql_stmt_core::kAltSqlStmtCore46: // alter resource pool case TRule_sql_stmt_core::kAltSqlStmtCore47: // drop resource pool + case TRule_sql_stmt_core::kAltSqlStmtCore48: // create resource pool classifier + case TRule_sql_stmt_core::kAltSqlStmtCore49: // alter resource pool classifier + case TRule_sql_stmt_core::kAltSqlStmtCore50: // drop resource pool classifier return false; } } diff --git a/ydb/library/yql/sql/v1/sql_query.cpp b/ydb/library/yql/sql/v1/sql_query.cpp index bbf890ddb2f9..e219753d1d6b 100644 --- a/ydb/library/yql/sql/v1/sql_query.cpp +++ b/ydb/library/yql/sql/v1/sql_query.cpp @@ -1362,6 +1362,69 @@ bool TSqlQuery::Statement(TVector& blocks, const TRule_sql_stmt_core& AddStatementToBlocks(blocks, BuildDropObjectOperation(Ctx.Pos(), objectId, "RESOURCE_POOL", false, {}, context)); break; } + case TRule_sql_stmt_core::kAltSqlStmtCore48: { + // create_resource_pool_classifier_stmt: CREATE RESOURCE POOL CLASSIFIER name WITH (k=v,...); + auto& node = core.GetAlt_sql_stmt_core48().GetRule_create_resource_pool_classifier_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_object_ref5().HasBlock1()) { + if (!ClusterExpr(node.GetRule_object_ref5().GetBlock1().GetRule_cluster_expr1(), + false, context.ServiceId, context.Cluster)) { + return false; + } + } + + const TString& objectId = Id(node.GetRule_object_ref5().GetRule_id_or_at2(), *this).second; + std::map kv; + if (!ParseResourcePoolClassifierSettings(kv, node.GetRule_with_table_settings6())) { + return false; + } + + AddStatementToBlocks(blocks, BuildCreateObjectOperation(Ctx.Pos(), objectId, "RESOURCE_POOL_CLASSIFIER", false, false, std::move(kv), context)); + break; + } + case TRule_sql_stmt_core::kAltSqlStmtCore49: { + // alter_resource_pool_classifier_stmt: ALTER RESOURCE POOL CLASSIFIER object_ref alter_resource_pool_classifier_action (COMMA alter_resource_pool_classifier_action)* + Ctx.BodyPart(); + const auto& node = core.GetAlt_sql_stmt_core49().GetRule_alter_resource_pool_classifier_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_object_ref5().HasBlock1()) { + if (!ClusterExpr(node.GetRule_object_ref5().GetBlock1().GetRule_cluster_expr1(), + false, context.ServiceId, context.Cluster)) { + return false; + } + } + + const TString& objectId = Id(node.GetRule_object_ref5().GetRule_id_or_at2(), *this).second; + std::map kv; + std::set toReset; + if (!ParseResourcePoolClassifierSettings(kv, toReset, node.GetRule_alter_resource_pool_classifier_action6())) { + return false; + } + + for (const auto& action : node.GetBlock7()) { + if (!ParseResourcePoolClassifierSettings(kv, toReset, action.GetRule_alter_resource_pool_classifier_action2())) { + return false; + } + } + + AddStatementToBlocks(blocks, BuildAlterObjectOperation(Ctx.Pos(), objectId, "RESOURCE_POOL_CLASSIFIER", std::move(kv), std::move(toReset), context)); + break; + } + case TRule_sql_stmt_core::kAltSqlStmtCore50: { + // drop_resource_pool_classifier_stmt: DROP RESOURCE POOL CLASSIFIER name; + auto& node = core.GetAlt_sql_stmt_core50().GetRule_drop_resource_pool_classifier_stmt1(); + TObjectOperatorContext context(Ctx.Scoped); + if (node.GetRule_object_ref5().HasBlock1()) { + if (!ClusterExpr(node.GetRule_object_ref5().GetBlock1().GetRule_cluster_expr1(), + false, context.ServiceId, context.Cluster)) { + return false; + } + } + + const TString& objectId = Id(node.GetRule_object_ref5().GetRule_id_or_at2(), *this).second; + AddStatementToBlocks(blocks, BuildDropObjectOperation(Ctx.Pos(), objectId, "RESOURCE_POOL_CLASSIFIER", false, {}, context)); + break; + } case TRule_sql_stmt_core::ALT_NOT_SET: Ctx.IncrementMonCounter("sql_errors", "UnknownStatement" + internalStatementName); AltNotImplemented("sql_stmt_core", core); diff --git a/ydb/library/yql/sql/v1/sql_translation.cpp b/ydb/library/yql/sql/v1/sql_translation.cpp index e06f5521d428..1041a1e16965 100644 --- a/ydb/library/yql/sql/v1/sql_translation.cpp +++ b/ydb/library/yql/sql/v1/sql_translation.cpp @@ -4712,26 +4712,90 @@ bool TSqlTranslation::ParseResourcePoolSettings(std::map bool TSqlTranslation::ParseResourcePoolSettings(std::map& result, std::set& toReset, const TRule_alter_resource_pool_action& alterAction) { switch (alterAction.Alt_case()) { case TRule_alter_resource_pool_action::kAltAlterResourcePoolAction1: { - const auto& action = alterAction.GetAlt_alter_resource_pool_action1().GetRule_alter_table_set_table_setting_uncompat1(); - if (!StoreResourcePoolSettingsEntry(IdEx(action.GetRule_an_id2(), *this), &action.GetRule_table_setting_value3(), result)) { + const auto& action = alterAction.GetAlt_alter_resource_pool_action1().GetRule_alter_table_set_table_setting_compat1(); + if (!StoreResourcePoolSettingsEntry(action.GetRule_alter_table_setting_entry3(), result)) { return false; } + for (const auto& entry : action.GetBlock4()) { + if (!StoreResourcePoolSettingsEntry(entry.GetRule_alter_table_setting_entry2(), result)) { + return false; + } + } return true; } case TRule_alter_resource_pool_action::kAltAlterResourcePoolAction2: { - const auto& action = alterAction.GetAlt_alter_resource_pool_action2().GetRule_alter_table_set_table_setting_compat1(); - if (!StoreResourcePoolSettingsEntry(action.GetRule_alter_table_setting_entry3(), result)) { + const auto& action = alterAction.GetAlt_alter_resource_pool_action2().GetRule_alter_table_reset_table_setting1(); + const TString firstKey = to_lower(IdEx(action.GetRule_an_id3(), *this).Name); + toReset.insert(firstKey); + for (const auto& key : action.GetBlock4()) { + toReset.insert(to_lower(IdEx(key.GetRule_an_id2(), *this).Name)); + } + return true; + } + case TRule_alter_resource_pool_action::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } +} + +bool TSqlTranslation::StoreResourcePoolClassifierSettingsEntry(const TIdentifier& id, const TRule_table_setting_value* value, std::map& result) { + YQL_ENSURE(value); + + const TString key = to_lower(id.Name); + if (result.find(key) != result.end()) { + Ctx.Error() << to_upper(key) << " duplicate keys"; + return false; + } + + switch (value->Alt_case()) { + case TRule_table_setting_value::kAltTableSettingValue2: + return StoreString(*value, result[key], Ctx, to_upper(key)); + + case TRule_table_setting_value::kAltTableSettingValue3: + return StoreInt(*value, result[key], Ctx, to_upper(key)); + + default: + Ctx.Error() << to_upper(key) << " value should be a string literal or integer"; + return false; + } + + return true; +} + +bool TSqlTranslation::StoreResourcePoolClassifierSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map& result) { + const TIdentifier id = IdEx(entry.GetRule_an_id1(), *this); + return StoreResourcePoolClassifierSettingsEntry(id, &entry.GetRule_table_setting_value3(), result); +} + +bool TSqlTranslation::ParseResourcePoolClassifierSettings(std::map& result, const TRule_with_table_settings& settingsNode) { + const auto& firstEntry = settingsNode.GetRule_table_settings_entry3(); + if (!StoreResourcePoolClassifierSettingsEntry(IdEx(firstEntry.GetRule_an_id1(), *this), &firstEntry.GetRule_table_setting_value3(), result)) { + return false; + } + for (const auto& block : settingsNode.GetBlock4()) { + const auto& entry = block.GetRule_table_settings_entry2(); + if (!StoreResourcePoolClassifierSettingsEntry(IdEx(entry.GetRule_an_id1(), *this), &entry.GetRule_table_setting_value3(), result)) { + return false; + } + } + return true; +} + +bool TSqlTranslation::ParseResourcePoolClassifierSettings(std::map& result, std::set& toReset, const TRule_alter_resource_pool_classifier_action& alterAction) { + switch (alterAction.Alt_case()) { + case TRule_alter_resource_pool_classifier_action::kAltAlterResourcePoolClassifierAction1: { + const auto& action = alterAction.GetAlt_alter_resource_pool_classifier_action1().GetRule_alter_table_set_table_setting_compat1(); + if (!StoreResourcePoolClassifierSettingsEntry(action.GetRule_alter_table_setting_entry3(), result)) { return false; } for (const auto& entry : action.GetBlock4()) { - if (!StoreResourcePoolSettingsEntry(entry.GetRule_alter_table_setting_entry2(), result)) { + if (!StoreResourcePoolClassifierSettingsEntry(entry.GetRule_alter_table_setting_entry2(), result)) { return false; } } return true; } - case TRule_alter_resource_pool_action::kAltAlterResourcePoolAction3: { - const auto& action = alterAction.GetAlt_alter_resource_pool_action3().GetRule_alter_table_reset_table_setting1(); + case TRule_alter_resource_pool_classifier_action::kAltAlterResourcePoolClassifierAction2: { + const auto& action = alterAction.GetAlt_alter_resource_pool_classifier_action2().GetRule_alter_table_reset_table_setting1(); const TString firstKey = to_lower(IdEx(action.GetRule_an_id3(), *this).Name); toReset.insert(firstKey); for (const auto& key : action.GetBlock4()) { @@ -4739,7 +4803,7 @@ bool TSqlTranslation::ParseResourcePoolSettings(std::map } return true; } - case TRule_alter_resource_pool_action::ALT_NOT_SET: + case TRule_alter_resource_pool_classifier_action::ALT_NOT_SET: Y_ABORT("You should change implementation according to grammar changes"); } } diff --git a/ydb/library/yql/sql/v1/sql_translation.h b/ydb/library/yql/sql/v1/sql_translation.h index 09e634511aa9..d31ecd6a3569 100644 --- a/ydb/library/yql/sql/v1/sql_translation.h +++ b/ydb/library/yql/sql/v1/sql_translation.h @@ -177,6 +177,8 @@ class TSqlTranslation: public TTranslation { bool StoreDataSourceSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map& result); bool StoreResourcePoolSettingsEntry(const TIdentifier& id, const TRule_table_setting_value* value, std::map& result); bool StoreResourcePoolSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map& result); + bool StoreResourcePoolClassifierSettingsEntry(const TIdentifier& id, const TRule_table_setting_value* value, std::map& result); + bool StoreResourcePoolClassifierSettingsEntry(const TRule_alter_table_setting_entry& entry, std::map& result); bool ResetTableSettingsEntry(const TIdentifier& id, TTableSettings& settings, ETableType tableType); TIdentifier GetTopicConsumerId(const TRule_topic_consumer_ref& node); @@ -233,6 +235,8 @@ class TSqlTranslation: public TTranslation { bool ParseViewQuery(std::map& features, const TRule_select_stmt& query); bool ParseResourcePoolSettings(std::map& result, const TRule_with_table_settings& settings); bool ParseResourcePoolSettings(std::map& result, std::set& toReset, const TRule_alter_resource_pool_action& alterAction); + bool ParseResourcePoolClassifierSettings(std::map& result, const TRule_with_table_settings& settings); + bool ParseResourcePoolClassifierSettings(std::map& result, std::set& toReset, const TRule_alter_resource_pool_classifier_action& alterAction); bool RoleNameClause(const TRule_role_name& node, TDeferredAtom& result, bool allowSystemRoles); bool RoleParameters(const TRule_create_user_option& node, TRoleParameters& result); bool PermissionNameClause(const TRule_permission_name_target& node, TVector& result, bool withGrantOption); diff --git a/ydb/library/yql/sql/v1/sql_ut.cpp b/ydb/library/yql/sql/v1/sql_ut.cpp index 90dae8d2ea58..dfed7cf49b6f 100644 --- a/ydb/library/yql/sql/v1/sql_ut.cpp +++ b/ydb/library/yql/sql/v1/sql_ut.cpp @@ -2494,7 +2494,7 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { Y_UNIT_TEST(AlterTableAddIndexWithIsNotSupported) { ExpectFailWithError("USE plato; ALTER TABLE table ADD INDEX idx LOCAL WITH (a=b, c=d, e=f) ON (col)", - "
:1:40: Error: local: alternative is not implemented yet: 725:7: local_index\n"); + "
:1:40: Error: local: alternative is not implemented yet: 728:7: local_index\n"); } Y_UNIT_TEST(AlterTableAlterIndexSetPartitioningIsCorrect) { @@ -6877,8 +6877,7 @@ Y_UNIT_TEST_SUITE(ResourcePool) { NYql::TAstParseResult res = SqlToYql(R"sql( USE plato; ALTER RESOURCE POOL MyResourcePool - SET (CONCURRENT_QUERY_LIMIT = 30, Weight = 5), - SET QUEUE_TYPE "UNORDERED", + SET (CONCURRENT_QUERY_LIMIT = 30, Weight = 5, QUEUE_TYPE = "UNORDERED"), RESET (Query_Cancel_After_Seconds, Query_Count_Limit); )sql"); UNIT_ASSERT_C(res.Root, res.Issues.ToString()); @@ -6917,3 +6916,87 @@ Y_UNIT_TEST_SUITE(ResourcePool) { UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); } } + +Y_UNIT_TEST_SUITE(ResourcePoolClassifier) { + Y_UNIT_TEST(CreateResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + RANK=20, + RESOURCE_POOL='wgUserQueries', + MEMBERNAME='yandex_query@abc' + ); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('('"membername" '"yandex_query@abc") '('"rank" (Int32 '"20")) '('"resource_pool" '"wgUserQueries"))#"); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("createObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(CreateResourcePoolClassifierWithBadArguments) { + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; + )sql" , "
:3:72: Error: Unexpected token ';' : syntax error...\n\n"); + + ExpectFailWithError(R"sql( + USE plato; + CREATE RESOURCE POOL CLASSIFIER MyResourcePoolClassifier WITH ( + DUPLICATE_SETTING="first_value", + DUPLICATE_SETTING="second_value" + ); + )sql" , "
:5:21: Error: DUPLICATE_SETTING duplicate keys\n"); + } + + Y_UNIT_TEST(AlterResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + ALTER RESOURCE POOL CLASSIFIER MyResourcePoolClassifier + SET (RANK = 30, Weight = 5, MEMBERNAME = "test@user"), + RESET (Resource_Pool); + )sql"); + UNIT_ASSERT_C(res.Root, res.Issues.ToString()); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, R"#(('mode 'alterObject))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('features '('('"membername" '"test@user") '('"rank" (Int32 '"30")) '('"weight" (Int32 '"5")))))#"); + UNIT_ASSERT_STRING_CONTAINS(line, R"#('('resetFeatures '('"resource_pool")))#"); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(DropResourcePoolClassifier) { + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP RESOURCE POOL CLASSIFIER MyResourcePoolClassifier; + )sql"); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_EQUAL(TString::npos, line.find("'features")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("dropObject")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } +} diff --git a/ydb/services/ext_index/metadata/manager.cpp b/ydb/services/ext_index/metadata/manager.cpp index 9e7b70128782..35b9226b5200 100644 --- a/ydb/services/ext_index/metadata/manager.cpp +++ b/ydb/services/ext_index/metadata/manager.cpp @@ -32,7 +32,7 @@ class TPreparationController: public NProvider::ISchemeDescribeController { void TManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& /*context*/) const { + const TInternalModificationContext& /*context*/, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { if (patchedObjects.size() != 1) { controller->OnPreparationProblem("modification possible for one object only"); return; diff --git a/ydb/services/ext_index/metadata/manager.h b/ydb/services/ext_index/metadata/manager.h index 8a97b4e5614b..73623d6a49db 100644 --- a/ydb/services/ext_index/metadata/manager.h +++ b/ydb/services/ext_index/metadata/manager.h @@ -10,7 +10,7 @@ class TManager: public NModifications::TGenericOperationsManager { protected: virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NModifications::TOperationParsingResult DoBuildPatchFromSettings( const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; diff --git a/ydb/services/metadata/abstract/decoder.cpp b/ydb/services/metadata/abstract/decoder.cpp index 0e62bd701cf2..c58cfcc90899 100644 --- a/ydb/services/metadata/abstract/decoder.cpp +++ b/ydb/services/metadata/abstract/decoder.cpp @@ -43,6 +43,17 @@ bool TDecoderBase::Read(const i32 columnIdx, ui64& result, const Ydb::Value& r) return false; } +bool TDecoderBase::Read(const i32 columnIdx, i64& result, const Ydb::Value& r) const { + if (columnIdx >= (i32)r.items().size() || columnIdx < 0) { + return false; + } + if (r.items()[columnIdx].has_int64_value()) { + result = r.items()[columnIdx].int64_value(); + return true; + } + return false; +} + bool TDecoderBase::Read(const i32 columnIdx, ui32& result, const Ydb::Value& r) const { if (columnIdx >= (i32)r.items().size() || columnIdx < 0) { return false; diff --git a/ydb/services/metadata/abstract/decoder.h b/ydb/services/metadata/abstract/decoder.h index 249b04d7c7b0..3b2d4eb3b896 100644 --- a/ydb/services/metadata/abstract/decoder.h +++ b/ydb/services/metadata/abstract/decoder.h @@ -14,6 +14,7 @@ class TDecoderBase { public: bool Read(const i32 columnIdx, TString& result, const Ydb::Value& r) const; bool Read(const i32 columnIdx, ui64& result, const Ydb::Value& r) const; + bool Read(const i32 columnIdx, i64& result, const Ydb::Value& r) const; bool Read(const i32 columnIdx, ui32& result, const Ydb::Value& r) const; bool ReadDebugProto(const i32 columnIdx, ::google::protobuf::Message& result, const Ydb::Value& r) const; bool ReadJson(const i32 columnIdx, NJson::TJsonValue& result, const Ydb::Value& r) const; diff --git a/ydb/services/metadata/initializer/manager.cpp b/ydb/services/metadata/initializer/manager.cpp index 0856618f2ebb..76d3c3696c21 100644 --- a/ydb/services/metadata/initializer/manager.cpp +++ b/ydb/services/metadata/initializer/manager.cpp @@ -5,7 +5,7 @@ namespace NKikimr::NMetadata::NInitializer { void TManager::DoPrepareObjectsBeforeModification(std::vector&& objects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& /*context*/) const + const TInternalModificationContext& /*context*/, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { controller->OnPreparationFinished(std::move(objects)); } diff --git a/ydb/services/metadata/initializer/manager.h b/ydb/services/metadata/initializer/manager.h index 036227b7b3cf..ffcfb694c6c6 100644 --- a/ydb/services/metadata/initializer/manager.h +++ b/ydb/services/metadata/initializer/manager.h @@ -15,7 +15,7 @@ class TManager: public NModifications::TGenericOperationsManager&& objects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& /*settings*/, TInternalModificationContext& context) const override; diff --git a/ydb/services/metadata/manager/abstract.h b/ydb/services/metadata/manager/abstract.h index 16ce69160259..41763dcc8300 100644 --- a/ydb/services/metadata/manager/abstract.h +++ b/ydb/services/metadata/manager/abstract.h @@ -19,6 +19,19 @@ namespace NKikimr::NMetadata::NModifications { using TOperationParsingResult = TConclusion; +class TAlterOperationContext { +private: + YDB_READONLY_DEF(TString, SessionId); + YDB_READONLY_DEF(TString, TransactionId); + YDB_READONLY_DEF(NInternal::TTableRecords, RestoreObjectIds); +public: + TAlterOperationContext(const TString& sessionId, const TString& transactionId, const NInternal::TTableRecords& RestoreObjectIds) + : SessionId(sessionId) + , TransactionId(transactionId) + , RestoreObjectIds(RestoreObjectIds) { + } +}; + class TColumnInfo { private: YDB_READONLY_FLAG(Primary, false); @@ -132,7 +145,7 @@ class IObjectOperationsManager: public IOperationsManager { TInternalModificationContext& context) const = 0; virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, typename IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const = 0; + const TInternalModificationContext& context, const TAlterOperationContext& alterContext) const = 0; public: using TPtr = std::shared_ptr>; @@ -149,8 +162,8 @@ class IObjectOperationsManager: public IOperationsManager { void PrepareObjectsBeforeModification(std::vector&& patchedObjects, typename NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const { - return DoPrepareObjectsBeforeModification(std::move(patchedObjects), controller, context); + const TInternalModificationContext& context, const TAlterOperationContext& alterContext) const { + return DoPrepareObjectsBeforeModification(std::move(patchedObjects), controller, context, alterContext); } }; diff --git a/ydb/services/metadata/manager/alter_impl.h b/ydb/services/metadata/manager/alter_impl.h index 9d8f984ec41b..434ab1c8b7f0 100644 --- a/ydb/services/metadata/manager/alter_impl.h +++ b/ydb/services/metadata/manager/alter_impl.h @@ -174,7 +174,7 @@ class TModificationActorImpl: public NActors::TActorBootstrappedPrepareObjectsBeforeModification(std::move(objects), InternalController, Context); + Manager->PrepareObjectsBeforeModification(std::move(objects), InternalController, Context, TAlterOperationContext(SessionId, TransactionId, RestoreObjectIds)); } } @@ -261,8 +261,8 @@ class TModificationActor: public TModificationActorImpl { if (!trPatch) { TBase::ExternalController->OnAlteringProblem("cannot found patch for object"); return false; - } else if (!trObject.TakeValuesFrom(*trPatch)) { - TBase::ExternalController->OnAlteringProblem("cannot patch object"); + } else if (const TConclusionStatus& status = objectPatched.MergeRecords(trObject, *trPatch); status.IsFail()) { + TBase::ExternalController->OnAlteringProblem(status.GetErrorMessage()); return false; } else if (!TObject::TDecoder::DeserializeFromRecord(objectPatched, trObject)) { TBase::ExternalController->OnAlteringProblem("cannot parse object after patch"); diff --git a/ydb/services/metadata/manager/common.h b/ydb/services/metadata/manager/common.h index 5bf79e690564..d6fbf7510236 100644 --- a/ydb/services/metadata/manager/common.h +++ b/ydb/services/metadata/manager/common.h @@ -1,9 +1,22 @@ #pragma once #include #include +#include + +namespace Ydb { + +class Value; + +}; namespace NKikimr::NMetadata { +namespace NInternal { + +class TTableRecord; + +}; + namespace NModifications { template @@ -26,6 +39,23 @@ class IAlterController { }; +class IColumnValuesMerger { +public: + using TPtr = std::shared_ptr; + virtual ~IColumnValuesMerger() = default; + + virtual TConclusionStatus Merge(Ydb::Value& value, const Ydb::Value& patch) const = 0; +}; + +class IRecordsMerger { +protected: + virtual IColumnValuesMerger::TPtr BuildMerger(const TString& columnName) const = 0; + +public: + virtual ~IRecordsMerger() = default; + virtual TConclusionStatus MergeRecords(NInternal::TTableRecord& value, const NInternal::TTableRecord& patch) const = 0; +}; + enum EEvents { EvRestoreFinished = EventSpaceBegin(TKikimrEvents::ES_METADATA_MANAGER), EvRestoreProblem, diff --git a/ydb/services/metadata/manager/object.cpp b/ydb/services/metadata/manager/object.cpp index 9493df23e6ef..3eb78d3cc73c 100644 --- a/ydb/services/metadata/manager/object.cpp +++ b/ydb/services/metadata/manager/object.cpp @@ -2,6 +2,34 @@ namespace NKikimr::NMetadata::NModifications { +namespace { + +class TDefaultColumnValuesMerger : public IColumnValuesMerger { +public: + virtual TConclusionStatus Merge(Ydb::Value& value, const Ydb::Value& patch) const override { + value = patch; + return TConclusionStatus::Success(); + } +}; + +} + +IColumnValuesMerger::TPtr TBaseObject::BuildMerger(const TString& columnName) const { + Y_UNUSED(columnName); + return std::make_shared(); +} + +TConclusionStatus TBaseObject::MergeRecords(NInternal::TTableRecord& value, const NInternal::TTableRecord& patch) const { + for (const auto& [columnId, patchValue] : patch.GetValues()) { + const auto& merger = BuildMerger(columnId); + const auto& status = merger->Merge(*value.GetMutableValuePtr(columnId), patchValue); + if (status.IsFail()) { + return status; + } + } + return TConclusionStatus::Success(); +} + Ydb::Table::CreateTableRequest TBaseObject::AddHistoryTableScheme(const Ydb::Table::CreateTableRequest& baseScheme, const TString& tableName) { Ydb::Table::CreateTableRequest result = baseScheme; result.add_primary_key("historyInstant"); diff --git a/ydb/services/metadata/manager/object.h b/ydb/services/metadata/manager/object.h index 60334da06551..0a6ad2c26b9c 100644 --- a/ydb/services/metadata/manager/object.h +++ b/ydb/services/metadata/manager/object.h @@ -1,4 +1,7 @@ #pragma once + +#include "common.h" + #include #include @@ -6,10 +9,13 @@ namespace NKikimr::NMetadata::NModifications { -class TBaseObject { +class TBaseObject : public IRecordsMerger { +protected: + virtual IColumnValuesMerger::TPtr BuildMerger(const TString& columnName) const override; + public: static Ydb::Table::CreateTableRequest AddHistoryTableScheme(const Ydb::Table::CreateTableRequest& baseScheme, const TString& tableName); - + virtual TConclusionStatus MergeRecords(NInternal::TTableRecord& value, const NInternal::TTableRecord& patch) const override; }; template diff --git a/ydb/services/metadata/manager/table_record.cpp b/ydb/services/metadata/manager/table_record.cpp index 2ac47275d1a0..69a7761cfadd 100644 --- a/ydb/services/metadata/manager/table_record.cpp +++ b/ydb/services/metadata/manager/table_record.cpp @@ -49,13 +49,6 @@ ui32 TTableRecord::CountIntersectColumns(const std::vector& columnIds) return result; } -bool TTableRecord::TakeValuesFrom(const TTableRecord& item) { - for (auto&& i : item.Values) { - Values[i.first] = i.second; - } - return true; -} - const Ydb::Value* TTableRecord::GetValuePtr(const TString& columnId) const { auto it = Values.find(columnId); if (it == Values.end()) { @@ -64,6 +57,10 @@ const Ydb::Value* TTableRecord::GetValuePtr(const TString& columnId) const { return &it->second; } +Ydb::Value* TTableRecord::GetMutableValuePtr(const TString& columnId) { + return &Values[columnId]; +} + TTableRecord& TTableRecord::SetColumn(const TString& columnId, const Ydb::Value& v) { Values[columnId] = v; return *this; @@ -291,4 +288,16 @@ TTableRecords TTableRecords::SelectColumns(const std::vector& columnIds return result; } +std::vector TTableRecords::GetTableRecords() const { + std::vector result; + result.reserve(Records.size()); + for (const Ydb::Value& record : Records) { + result.emplace_back(); + for (size_t i = 0; i < std::min(Columns.size(), static_cast(record.items_size())); ++i) { + result.back().SetColumn(Columns[i].name(), record.items(i)); + } + } + return result; +} + } diff --git a/ydb/services/metadata/manager/table_record.h b/ydb/services/metadata/manager/table_record.h index 1b800d6acf80..1df19d0e0a88 100644 --- a/ydb/services/metadata/manager/table_record.h +++ b/ydb/services/metadata/manager/table_record.h @@ -21,8 +21,8 @@ class TTableRecord { bool HasColumns(const std::vector& columnIds) const; ui32 CountIntersectColumns(const std::vector& columnIds) const; bool SameColumns(const TTableRecord& item) const; - bool TakeValuesFrom(const TTableRecord& item); const Ydb::Value* GetValuePtr(const TString& columnId) const; + Ydb::Value* GetMutableValuePtr(const TString& columnId); }; class TTableRecords { @@ -45,6 +45,7 @@ class TTableRecords { public: TTableRecords SelectColumns(const std::vector& columnIds) const; + std::vector GetTableRecords() const; Ydb::Table::ExecuteDataQueryRequest BuildInsertQuery(const TString& tablePath) const; Ydb::Table::ExecuteDataQueryRequest BuildUpsertQuery(const TString& tablePath) const; diff --git a/ydb/services/metadata/manager/ydb_value_operator.cpp b/ydb/services/metadata/manager/ydb_value_operator.cpp index b90bf02b85da..45802e78d0a6 100644 --- a/ydb/services/metadata/manager/ydb_value_operator.cpp +++ b/ydb/services/metadata/manager/ydb_value_operator.cpp @@ -16,7 +16,7 @@ bool TYDBValue::IsSameType(const Ydb::Value& v, const Ydb::Type& type) { return v.has_uint64_value(); } else if (type.type_id() == Ydb::Type::STRING) { return v.has_bytes_value(); - } else if (type.type_id() == Ydb::Type::UTF8) { + } else if (type.type_id() == Ydb::Type::UTF8 || type.type_id() == Ydb::Type::JSON_DOCUMENT) { return v.has_text_value(); } Y_ABORT_UNLESS(false); @@ -60,13 +60,15 @@ TString TYDBValue::TypeToString(const Ydb::Type& type) { } else if (type.type_id() == Ydb::Type::UINT32) { return "Uint32"; } else if (type.type_id() == Ydb::Type::INT64) { - return "Uint64"; + return "Int64"; } else if (type.type_id() == Ydb::Type::UINT64) { return "Uint64"; } else if (type.type_id() == Ydb::Type::STRING) { return "String"; } else if (type.type_id() == Ydb::Type::UTF8) { return "Utf8"; + } else if (type.type_id() == Ydb::Type::JSON_DOCUMENT) { + return "JsonDocument"; } else { Y_ABORT_UNLESS(false); } @@ -120,6 +122,12 @@ Ydb::Value TYDBValue::UInt64(const ui64 value) { return result; } +Ydb::Value TYDBValue::Int64(const i64 value) { + Ydb::Value result; + result.set_int64_value(value); + return result; +} + Ydb::Value TYDBValue::Bool(const bool value) { Ydb::Value result; result.set_bool_value(value); diff --git a/ydb/services/metadata/manager/ydb_value_operator.h b/ydb/services/metadata/manager/ydb_value_operator.h index 28983a16f4ef..0580e53ac2e1 100644 --- a/ydb/services/metadata/manager/ydb_value_operator.h +++ b/ydb/services/metadata/manager/ydb_value_operator.h @@ -41,6 +41,7 @@ class TYDBValue { static Ydb::Value Utf8(const TString& value); static Ydb::Value Utf8(const TStringBuf& value); static Ydb::Value UInt64(const ui64 value); + static Ydb::Value Int64(const i64 value); static Ydb::Value UInt32(const ui32 value); static Ydb::Value Bool(const bool value); }; diff --git a/ydb/services/metadata/secret/manager.cpp b/ydb/services/metadata/secret/manager.cpp index 8f4d28516c83..882c282064f4 100644 --- a/ydb/services/metadata/secret/manager.cpp +++ b/ydb/services/metadata/secret/manager.cpp @@ -6,7 +6,7 @@ namespace NKikimr::NMetadata::NSecret { void TAccessManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const { + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { if (context.GetActivityType() == IOperationsManager::EActivityType::Alter) { controller->OnPreparationProblem("access object cannot be modified"); return; @@ -88,7 +88,7 @@ NModifications::TOperationParsingResult TSecretManager::DoBuildPatchFromSettings } void TSecretManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const { + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { if (!!context.GetExternalData().GetUserToken()) { for (auto&& i : patchedObjects) { if (i.GetOwnerUserId() != context.GetExternalData().GetUserToken()->GetUserSID()) { diff --git a/ydb/services/metadata/secret/manager.h b/ydb/services/metadata/secret/manager.h index ebaac399d887..df231cdb65f0 100644 --- a/ydb/services/metadata/secret/manager.h +++ b/ydb/services/metadata/secret/manager.h @@ -10,7 +10,7 @@ class TSecretManager: public NModifications::TGenericOperationsManager protected: virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NModifications::TOperationParsingResult DoBuildPatchFromSettings( const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; @@ -20,7 +20,7 @@ class TAccessManager: public NModifications::TGenericOperationsManager protected: virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context) const override; + const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; virtual NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; diff --git a/ydb/tests/tools/kqprun/kqprun.cpp b/ydb/tests/tools/kqprun/kqprun.cpp index c5a80cda33b9..9f97c8af1fac 100644 --- a/ydb/tests/tools/kqprun/kqprun.cpp +++ b/ydb/tests/tools/kqprun/kqprun.cpp @@ -296,7 +296,7 @@ class TMain : public TMainClassArgs { TablesMapping[tableName] = filePath; }); - options.AddLongOption('c', "app-config", "File with app config (TAppConfig for ydb tennant)") + options.AddLongOption('c', "app-config", "File with app config (TAppConfig for ydb tenant)") .RequiredArgument("file") .DefaultValue("./configuration/app_config.conf") .Handler1([this](const NLastGetopt::TOptsParser* option) {