diff --git a/ydb/library/backup/backup.cpp b/ydb/library/backup/backup.cpp index ee1428561ffb..c0a9d0400326 100644 --- a/ydb/library/backup/backup.cpp +++ b/ydb/library/backup/backup.cpp @@ -610,6 +610,32 @@ void BackupTopic(TDriver driver, const TString& dbPath, const TFsPath& fsBackupF BackupPermissions(driver, dbPath, fsBackupFolder); } +namespace { + +NCoordination::TNodeDescription DescribeCoordinationNode(TDriver driver, const TString& path) { + NCoordination::TClient client(driver); + auto status = NConsoleClient::RetryFunction([&]() { + return client.DescribeNode(path).ExtractValueSync(); + }); + VerifyStatus(status, "describe coordination node to build a backup"); + return status.ExtractResult(); +} + +} + +void BackupCoordinationNode(TDriver driver, const TString& dbPath, const TFsPath& fsBackupFolder) { + Y_ENSURE(!dbPath.empty()); + LOG_I("Backup coordination node " << dbPath.Quote() << " to " << fsBackupFolder.GetPath().Quote()); + + const auto nodeDescription = DescribeCoordinationNode(driver, dbPath); + + Ydb::Coordination::CreateNodeRequest creationRequest; + nodeDescription.SerializeTo(creationRequest); + + WriteProtoToFile(creationRequest, fsBackupFolder, NDump::NFiles::CreateCoordinationNode()); + BackupPermissions(driver, dbPath, fsBackupFolder); +} + void CreateClusterDirectory(const TDriver& driver, const TString& path, bool rootBackupDir = false) { if (rootBackupDir) { LOG_I("Create temporary directory " << path.Quote()); @@ -700,6 +726,9 @@ void BackupFolderImpl(TDriver driver, const TString& dbPrefix, const TString& ba if (dbIt.IsTopic()) { BackupTopic(driver, dbIt.GetFullPath(), childFolderPath); } + if (dbIt.IsCoordinationNode()) { + BackupCoordinationNode(driver, dbIt.GetFullPath(), childFolderPath); + } dbIt.Next(); } } diff --git a/ydb/library/backup/db_iterator.h b/ydb/library/backup/db_iterator.h index 35fe95be71ba..388f9ef40578 100644 --- a/ydb/library/backup/db_iterator.h +++ b/ydb/library/backup/db_iterator.h @@ -137,6 +137,10 @@ class TDbIterator { return GetCurrentNode()->Type == NScheme::ESchemeEntryType::Topic; } + bool IsCoordinationNode() const { + return GetCurrentNode()->Type == NScheme::ESchemeEntryType::CoordinationNode; + } + bool IsDir() const { return GetCurrentNode()->Type == NScheme::ESchemeEntryType::Directory; } diff --git a/ydb/library/backup/ya.make b/ydb/library/backup/ya.make index 9aac8674632f..91e6a6a203e6 100644 --- a/ydb/library/backup/ya.make +++ b/ydb/library/backup/ya.make @@ -12,6 +12,7 @@ PEERDIR( ydb/public/lib/ydb_cli/dump/util ydb/public/lib/yson_value ydb/public/lib/ydb_cli/dump/files + ydb/public/sdk/cpp/src/client/coordination ydb/public/sdk/cpp/src/client/draft ydb/public/sdk/cpp/src/client/driver ydb/public/sdk/cpp/src/client/proto diff --git a/ydb/public/lib/ydb_cli/dump/files/files.cpp b/ydb/public/lib/ydb_cli/dump/files/files.cpp index ff87c4b3eb0b..0a5b4e635d29 100644 --- a/ydb/public/lib/ydb_cli/dump/files/files.cpp +++ b/ydb/public/lib/ydb_cli/dump/files/files.cpp @@ -8,6 +8,7 @@ enum EFilesType { CHANGEFEED_DESCRIPTION, TOPIC_DESCRIPTION, CREATE_TOPIC, + CREATE_COORDINATION_NODE, INCOMPLETE_DATA, INCOMPLETE, EMPTY, @@ -20,6 +21,7 @@ static constexpr TFileInfo FILES_INFO[] = { {"changefeed_description.pb", "changefeed"}, {"topic_description.pb", "topic"}, {"create_topic.pb", "topic"}, + {"create_coordination_node.pb", "coordination node"}, {"incomplete.csv", "incomplete"}, {"incomplete", "incomplete"}, {"empty_dir", "empty_dir"}, @@ -46,6 +48,10 @@ const TFileInfo& CreateTopic() { return FILES_INFO[CREATE_TOPIC]; } +const TFileInfo& CreateCoordinationNode() { + return FILES_INFO[CREATE_COORDINATION_NODE]; +} + const TFileInfo& IncompleteData() { return FILES_INFO[INCOMPLETE_DATA]; } diff --git a/ydb/public/lib/ydb_cli/dump/files/files.h b/ydb/public/lib/ydb_cli/dump/files/files.h index 904a86009b1d..b96aadbc6bf2 100644 --- a/ydb/public/lib/ydb_cli/dump/files/files.h +++ b/ydb/public/lib/ydb_cli/dump/files/files.h @@ -12,6 +12,7 @@ const TFileInfo& Permissions(); const TFileInfo& Changefeed(); const TFileInfo& TopicDescription(); const TFileInfo& CreateTopic(); +const TFileInfo& CreateCoordinationNode(); const TFileInfo& IncompleteData(); const TFileInfo& Incomplete(); const TFileInfo& Empty(); diff --git a/ydb/public/lib/ydb_cli/dump/restore_impl.cpp b/ydb/public/lib/ydb_cli/dump/restore_impl.cpp index 0a9efc27ffc7..4705d5126db8 100644 --- a/ydb/public/lib/ydb_cli/dump/restore_impl.cpp +++ b/ydb/public/lib/ydb_cli/dump/restore_impl.cpp @@ -66,6 +66,10 @@ Ydb::Topic::CreateTopicRequest ReadTopicCreationRequest(const TFsPath& fsDirPath return ReadProtoFromFile(fsDirPath, log, NFiles::CreateTopic()); } +Ydb::Coordination::CreateNodeRequest ReadCoordinationNodeCreationRequest(const TFsPath& fsDirPath, const TLog* log) { + return ReadProtoFromFile(fsDirPath, log, NDump::NFiles::CreateCoordinationNode()); +} + Ydb::Scheme::ModifyPermissionsRequest ReadPermissions(const TFsPath& fsDirPath, const TLog* log) { return ReadProtoFromFile(fsDirPath, log, NFiles::Permissions()); } @@ -101,7 +105,7 @@ TStatus WaitForIndexBuild(TOperationClient& client, const TOperation::TOperation return operation.Status(); } } - NConsoleClient::ExponentialBackoff(retrySleep, TDuration::Minutes(1)); + ExponentialBackoff(retrySleep, TDuration::Minutes(1)); } } @@ -159,12 +163,24 @@ TRestoreResult CheckExistenceAndType(TSchemeClient& client, const TString& dbPat TStatus CreateTopic(TTopicClient& client, const TString& dbPath, const Ydb::Topic::CreateTopicRequest& request) { const auto settings = TCreateTopicSettings(request); - auto result = NConsoleClient::RetryFunction([&]() { + auto result = RetryFunction([&]() { return client.CreateTopic(dbPath, settings).ExtractValueSync(); }); return result; } +TStatus CreateCoordinationNode( + NCoordination::TClient& client, + const TString& dbPath, + const Ydb::Coordination::CreateNodeRequest& request) +{ + const auto settings = NCoordination::TCreateNodeSettings(request.config()); + auto result = RetryFunction([&]() { + return client.CreateNode(dbPath, settings).ExtractValueSync(); + }); + return result; +} + } // anonymous namespace NPrivate { @@ -228,6 +244,7 @@ TRestoreClient::TRestoreClient(const TDriver& driver, const std::shared_ptr result; switch (entry.Type) { - case ESchemeEntryType::Directory: { - result = NConsoleClient::RemoveDirectoryRecursive(SchemeClient, TableClient, nullptr, &QueryClient, + case ESchemeEntryType::Directory: + result = RemoveDirectoryRecursive(SchemeClient, TableClient, nullptr, &QueryClient, TString{fullPath}, ERecursiveRemovePrompt::Never, {}, true, false); break; - } - case ESchemeEntryType::Table: { + case ESchemeEntryType::Table: result = TableClient.RetryOperationSync([&path = fullPath](TSession session) { return session.DropTable(path).GetValueSync(); }); break; - } - case ESchemeEntryType::View: { + case ESchemeEntryType::View: result = QueryClient.RetryQuerySync([&path = fullPath](NQuery::TSession session) { return session.ExecuteQuery(std::format("DROP VIEW IF EXISTS `{}`;", path), NQuery::TTxControl::NoTx()).ExtractValueSync(); }); break; - } case ESchemeEntryType::Topic: - result = NConsoleClient::RetryFunction([&client = TopicClient, &path = fullPath]() { + result = RetryFunction([&client = TopicClient, &path = fullPath]() { return client.DropTopic(path).ExtractValueSync(); }); break; + case ESchemeEntryType::CoordinationNode: + result = RetryFunction([&client = CoordinationNodeClient, &path = fullPath]() { + return client.DropNode(path).ExtractValueSync(); + }); + break; default: break; } @@ -407,6 +426,10 @@ TRestoreResult TRestoreClient::RestoreFolder( return RestoreTopic(fsPath, objectDbPath, settings, oldEntries.contains(objectDbPath)); } + if (IsFileExists(fsPath.Child(NFiles::CreateCoordinationNode().FileName))) { + return RestoreCoordinationNode(fsPath, objectDbPath, settings, oldEntries.contains(objectDbPath)); + } + if (IsFileExists(fsPath.Child(NFiles::Empty().FileName))) { return RestoreEmptyDir(fsPath, objectDbPath, settings, oldEntries.contains(objectDbPath)); } @@ -426,6 +449,8 @@ TRestoreResult TRestoreClient::RestoreFolder( ViewRestorationCalls.emplace_back(child, dbRestoreRoot, Join('/', dbPathRelativeToRestoreRoot, child.GetName()), settings, oldEntries.contains(childDbPath)); } else if (IsFileExists(child.Child(NFiles::CreateTopic().FileName))) { result = RestoreTopic(child, childDbPath, settings, oldEntries.contains(childDbPath)); + } else if (IsFileExists(child.Child(NFiles::CreateCoordinationNode().FileName))) { + result = RestoreCoordinationNode(child, childDbPath, settings, oldEntries.contains(childDbPath)); } else if (child.IsDirectory()) { result = RestoreFolder(child, dbRestoreRoot, Join('/', dbPathRelativeToRestoreRoot, child.GetName()), settings, oldEntries); } @@ -525,6 +550,34 @@ TRestoreResult TRestoreClient::RestoreTopic( return Result(dbPath, std::move(result)); } +TRestoreResult TRestoreClient::RestoreCoordinationNode( + const TFsPath& fsPath, + const TString& dbPath, + const TRestoreSettings& settings, + bool isAlreadyExisting) +{ + LOG_D("Process " << fsPath.GetPath().Quote()); + + if (auto error = ErrorOnIncomplete(fsPath)) { + return *error; + } + + LOG_I("Restore coordination node " << fsPath.GetPath().Quote() << " to " << dbPath.Quote()); + + if (settings.DryRun_) { + return CheckExistenceAndType(SchemeClient, dbPath, NScheme::ESchemeEntryType::CoordinationNode); + } + + const auto creationRequest = ReadCoordinationNodeCreationRequest(fsPath, Log.get()); + auto result = CreateCoordinationNode(CoordinationNodeClient, dbPath, creationRequest); + if (result.IsSuccess()) { + LOG_D("Created " << dbPath.Quote()); + return RestorePermissions(fsPath, dbPath, settings, isAlreadyExisting); + } + LOG_E("Failed to create " << dbPath.Quote()); + return Result(dbPath, std::move(result)); +} + TRestoreResult TRestoreClient::RestoreTable( const TFsPath& fsPath, const TString& dbPath, @@ -862,7 +915,7 @@ TRestoreResult TRestoreClient::RestoreIndexes(const TString& dbPath, const TTabl return Result(dbPath, std::move(waitForIndexBuildStatus)); } - auto forgetStatus = NConsoleClient::RetryFunction([&]() { + auto forgetStatus = RetryFunction([&]() { return OperationClient.Forget(buildIndexId).GetValueSync(); }); if (!forgetStatus.IsSuccess()) { diff --git a/ydb/public/lib/ydb_cli/dump/restore_impl.h b/ydb/public/lib/ydb_cli/dump/restore_impl.h index e46469e0375e..e52159c0c838 100644 --- a/ydb/public/lib/ydb_cli/dump/restore_impl.h +++ b/ydb/public/lib/ydb_cli/dump/restore_impl.h @@ -2,6 +2,7 @@ #include "dump.h" +#include #include #include #include @@ -129,6 +130,7 @@ class TRestoreClient { TRestoreResult RestoreTable(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, bool isAlreadyExisting); TRestoreResult RestoreView(const TFsPath& fsPath, const TString& dbRestoreRoot, const TString& dbPathRelativeToRestoreRoot, const TRestoreSettings& settings, bool isAlreadyExisting); TRestoreResult RestoreTopic(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, bool isAlreadyExisting); + TRestoreResult RestoreCoordinationNode(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, bool isAlreadyExisting); TRestoreResult CheckSchema(const TString& dbPath, const NTable::TTableDescription& desc); TRestoreResult RestoreData(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, const NTable::TTableDescription& desc); @@ -154,6 +156,7 @@ class TRestoreClient { NScheme::TSchemeClient SchemeClient; NTable::TTableClient TableClient; NTopic::TTopicClient TopicClient; + NCoordination::TClient CoordinationNodeClient; NQuery::TQueryClient QueryClient; std::shared_ptr Log; diff --git a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/coordination/coordination.h b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/coordination/coordination.h index 8d4fa0f1f452..d1893ee6ff3e 100644 --- a/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/coordination/coordination.h +++ b/ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/coordination/coordination.h @@ -4,9 +4,11 @@ namespace Ydb { namespace Coordination { + class Config; + class CreateNodeRequest; class DescribeNodeResult; - class SemaphoreSession; class SemaphoreDescription; + class SemaphoreSession; } } @@ -107,6 +109,8 @@ class TNodeDescription { const std::vector& GetEffectivePermissions() const; const Ydb::Coordination::DescribeNodeResult& GetProto() const; + void SerializeTo(Ydb::Coordination::CreateNodeRequest& creationRequest) const; + private: struct TImpl; std::shared_ptr Impl_; @@ -189,7 +193,10 @@ struct TNodeSettings : public TOperationRequestSettings { FLUENT_SETTING_DEFAULT(ERateLimiterCountersMode, RateLimiterCountersMode, ERateLimiterCountersMode::UNSET); }; -struct TCreateNodeSettings : public TNodeSettings { }; +struct TCreateNodeSettings : public TNodeSettings { + TCreateNodeSettings() = default; + TCreateNodeSettings(const Ydb::Coordination::Config& config); +}; struct TAlterNodeSettings : public TNodeSettings { }; struct TDropNodeSettings : public TOperationRequestSettings { }; struct TDescribeNodeSettings : public TOperationRequestSettings { }; diff --git a/ydb/public/sdk/cpp/src/client/coordination/coordination.cpp b/ydb/public/sdk/cpp/src/client/coordination/coordination.cpp index 54a37d1988bd..4b42ce3ba6d5 100644 --- a/ydb/public/sdk/cpp/src/client/coordination/coordination.cpp +++ b/ydb/public/sdk/cpp/src/client/coordination/coordination.cpp @@ -17,7 +17,6 @@ namespace NCoordination { using NThreading::TFuture; using NThreading::TPromise; using NThreading::NewPromise; -using NYdbGrpc::TQueueClientFixedEvent; namespace { @@ -35,30 +34,6 @@ inline TResultPromise NewResultPromise() { //////////////////////////////////////////////////////////////////////////////// -template -void ConvertSettingsToProtoConfig( - const Settings& settings, - Ydb::Coordination::Config* config) -{ - if (settings.SelfCheckPeriod_) { - config->set_self_check_period_millis(settings.SelfCheckPeriod_->MilliSeconds()); - } - if (settings.SessionGracePeriod_) { - config->set_session_grace_period_millis(settings.SessionGracePeriod_->MilliSeconds()); - } - if (settings.ReadConsistencyMode_ != EConsistencyMode::UNSET) { - config->set_read_consistency_mode(static_cast(settings.ReadConsistencyMode_)); - } - if (settings.AttachConsistencyMode_ != EConsistencyMode::UNSET) { - config->set_attach_consistency_mode(static_cast(settings.AttachConsistencyMode_)); - } - if (settings.RateLimiterCountersMode_ != ERateLimiterCountersMode::UNSET) { - config->set_rate_limiter_counters_mode(static_cast(settings.RateLimiterCountersMode_)); - } -} - -//////////////////////////////////////////////////////////////////////////////// - std::string GenerateProtectionKey(size_t size) { std::string key; if (size > 0) { @@ -89,6 +64,12 @@ struct TNodeDescription::TImpl { Proto_ = desc; } + void SerializeTo(Ydb::Coordination::CreateNodeRequest& creationRequest) { + auto& config = *creationRequest.mutable_config(); + config.CopyFrom(Proto_.config()); + config.clear_path(); + } + std::optional SelfCheckPeriod_; std::optional SessionGracePeriod_; EConsistencyMode ReadConsistencyMode_; @@ -136,6 +117,10 @@ const Ydb::Coordination::DescribeNodeResult& TNodeDescription::GetProto() const return Impl_->Proto_; } +void TNodeDescription::SerializeTo(Ydb::Coordination::CreateNodeRequest& creationRequest) const { + return Impl_->SerializeTo(creationRequest); +} + //////////////////////////////////////////////////////////////////////////////// TSemaphoreSession::TSemaphoreSession() { @@ -1807,6 +1792,52 @@ class TSessionContext : public TThrRefBase { //////////////////////////////////////////////////////////////////////////////// +namespace { + +template +void ConvertSettingsToProtoConfig( + const Settings& settings, + Ydb::Coordination::Config* config) +{ + if (settings.SelfCheckPeriod_) { + config->set_self_check_period_millis(settings.SelfCheckPeriod_->MilliSeconds()); + } + if (settings.SessionGracePeriod_) { + config->set_session_grace_period_millis(settings.SessionGracePeriod_->MilliSeconds()); + } + if (settings.ReadConsistencyMode_ != EConsistencyMode::UNSET) { + config->set_read_consistency_mode(static_cast(settings.ReadConsistencyMode_)); + } + if (settings.AttachConsistencyMode_ != EConsistencyMode::UNSET) { + config->set_attach_consistency_mode(static_cast(settings.AttachConsistencyMode_)); + } + if (settings.RateLimiterCountersMode_ != ERateLimiterCountersMode::UNSET) { + config->set_rate_limiter_counters_mode(static_cast(settings.RateLimiterCountersMode_)); + } +} + +} + +TCreateNodeSettings::TCreateNodeSettings(const Ydb::Coordination::Config& config) { + if (config.self_check_period_millis() != 0u) { + SelfCheckPeriod(TDuration::MilliSeconds(config.self_check_period_millis())); + } + if (config.session_grace_period_millis() != 0u) { + SessionGracePeriod(TDuration::MilliSeconds(config.session_grace_period_millis())); + } + if (config.read_consistency_mode() != Ydb::Coordination::CONSISTENCY_MODE_UNSET) { + ReadConsistencyMode(static_cast(config.read_consistency_mode())); + } + if (config.attach_consistency_mode() != Ydb::Coordination::CONSISTENCY_MODE_UNSET) { + AttachConsistencyMode(static_cast(config.attach_consistency_mode())); + } + if (config.rate_limiter_counters_mode() != Ydb::Coordination::RATE_LIMITER_COUNTERS_MODE_UNSET) { + RateLimiterCountersMode(static_cast(config.rate_limiter_counters_mode())); + } +} + +//////////////////////////////////////////////////////////////////////////////// + class TClient::TImpl : public TClientImplCommon { public: TImpl(std::shared_ptr&& connections, const TCommonClientSettings& settings) diff --git a/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp b/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp index 95fcb663bce8..25733d338837 100644 --- a/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp +++ b/ydb/services/ydb/backup_ut/ydb_backup_ut.cpp @@ -6,13 +6,14 @@ #include #include #include +#include #include #include #include #include +#include #include #include -#include #include @@ -745,6 +746,56 @@ void TestTopicSettingsArePreserved( checkDescription(DescribeTopic(topicClient, topic), DEBUG_HINT); } +void CreateCoordinationNode( + NCoordination::TClient& client, const std::string& path, const NCoordination::TCreateNodeSettings& settings) +{ + const auto result = client.CreateNode(path, settings).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); +} + +NCoordination::TNodeDescription DescribeCoordinationNode(NCoordination::TClient& client, const std::string& path) { + const auto result = client.DescribeNode(path).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); + return result.GetResult(); +} + +void DropCoordinationNode(NCoordination::TClient& client, const std::string& path) { + const auto result = client.DropNode(path).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); +} + +void TestCoordinationNodeSettingsArePreserved( + const std::string& path, + NCoordination::TClient& nodeClient, + TBackupFunction&& backup, + TRestoreFunction&& restore +) { + const auto settings = NCoordination::TCreateNodeSettings() + .SelfCheckPeriod(TDuration::Seconds(2)) + .SessionGracePeriod(TDuration::Seconds(30)) + .ReadConsistencyMode(NCoordination::EConsistencyMode::STRICT_MODE) + .AttachConsistencyMode(NCoordination::EConsistencyMode::STRICT_MODE) + .RateLimiterCountersMode(NCoordination::ERateLimiterCountersMode::DETAILED); + + CreateCoordinationNode(nodeClient, path, settings); + + const auto checkDescription = [&](const NCoordination::TNodeDescription& description, const TString& debugHint) { + UNIT_ASSERT_VALUES_EQUAL_C(*description.GetSelfCheckPeriod(), *settings.SelfCheckPeriod_, debugHint); + UNIT_ASSERT_VALUES_EQUAL_C(*description.GetSessionGracePeriod(), *settings.SessionGracePeriod_, debugHint); + UNIT_ASSERT_VALUES_EQUAL_C(description.GetReadConsistencyMode(), settings.ReadConsistencyMode_, debugHint); + UNIT_ASSERT_VALUES_EQUAL_C(description.GetAttachConsistencyMode(), settings.AttachConsistencyMode_, debugHint); + UNIT_ASSERT_VALUES_EQUAL_C(description.GetRateLimiterCountersMode(), settings.RateLimiterCountersMode_, debugHint); + }; + checkDescription(DescribeCoordinationNode(nodeClient, path), DEBUG_HINT); + + backup(); + + DropCoordinationNode(nodeClient, path); + + restore(); + checkDescription(DescribeCoordinationNode(nodeClient, path), DEBUG_HINT); +} + } Y_UNIT_TEST_SUITE(BackupRestore) { @@ -1016,6 +1067,23 @@ Y_UNIT_TEST_SUITE(BackupRestore) { ); } + void TestKesusBackupRestore() { + TKikimrWithGrpcAndRootSchema server; + auto driver = TDriver(TDriverConfig().SetEndpoint(Sprintf("localhost:%u", server.GetPort()))); + NCoordination::TClient nodeClient(driver); + TTempDir tempDir; + const auto& pathToBackup = tempDir.Path(); + + const std::string kesus = "/Root/kesus"; + + TestCoordinationNodeSettingsArePreserved( + kesus, + nodeClient, + CreateBackupLambda(driver, pathToBackup), + CreateRestoreLambda(driver, pathToBackup) + ); + } + Y_UNIT_TEST_ALL_PROTO_ENUM_VALUES(TestAllSchemeObjectTypes, NKikimrSchemeOp::EPathType) { using namespace NKikimrSchemeOp; @@ -1053,7 +1121,8 @@ Y_UNIT_TEST_SUITE(BackupRestore) { case EPathTypeResourcePool: break; // https://github.com/ydb-platform/ydb/issues/10440 case EPathTypeKesus: - break; // https://github.com/ydb-platform/ydb/issues/10444 + TestKesusBackupRestore(); + break; case EPathTypeColumnStore: case EPathTypeColumnTable: break; // https://github.com/ydb-platform/ydb/issues/10459