diff --git a/cloud/filestore/config/storage.proto b/cloud/filestore/config/storage.proto index a949a7e598..ff3900dc87 100644 --- a/cloud/filestore/config/storage.proto +++ b/cloud/filestore/config/storage.proto @@ -318,4 +318,16 @@ message TStorageConfig // Number of ranges with zero compaction stats to delete per tx. optional uint32 MaxZeroCompactionRangesToDeletePerTx = 367; + + // Mapping allowing for multiple fs ids to point to the same fs + message TFilestoreAliasEntry + { + optional string Alias = 1; + optional string FsId = 2; + } + message TFilestoreAliases + { + repeated TFilestoreAliasEntry Entries = 1; + } + optional TFilestoreAliases FilestoreAliases = 368; } diff --git a/cloud/filestore/libs/storage/core/config.cpp b/cloud/filestore/libs/storage/core/config.cpp index 50e13f9654..da89c3e08a 100644 --- a/cloud/filestore/libs/storage/core/config.cpp +++ b/cloud/filestore/libs/storage/core/config.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -12,6 +13,10 @@ namespace { //////////////////////////////////////////////////////////////////////////////// +// Type alias is used here because using THashMap inside macro +// will mess it up because of the comma in the type. +using TAliasMap = THashMap; + #define FILESTORE_STORAGE_CONFIG(xxx) \ xxx(SchemeShardDir, TString, "/Root" )\ \ @@ -170,6 +175,8 @@ namespace { xxx(BlobCompressionRate, ui32, 0 )\ xxx(BlobCompressionCodec, TString, "lz4" )\ \ + xxx(FilestoreAliases, TAliasMap, {} )\ + \ xxx(MaxZeroCompactionRangesToDeletePerTx, ui32, 10000 )\ // FILESTORE_STORAGE_CONFIG @@ -189,12 +196,40 @@ bool IsEmpty(const T& t) return !t; } +template <> +bool IsEmpty(const NCloud::NProto::TCertificate& value) +{ + return !value.GetCertFile() && !value.GetCertPrivateKeyFile(); +} + +template <> +bool IsEmpty(const NProto::TStorageConfig::TFilestoreAliases& value) +{ + return value.GetEntries().empty(); +} + template TTarget ConvertValue(const TSource& value) { return static_cast(value); } +template <> +TCertificate ConvertValue(const NCloud::NProto::TCertificate& value) +{ + return {value.GetCertFile(), value.GetCertPrivateKeyFile()}; +} + +template <> +TAliasMap ConvertValue(const NProto::TStorageConfig::TFilestoreAliases& value) +{ + TAliasMap result; + for (const auto& entry: value.GetEntries()) { + result[entry.GetAlias()] = entry.GetFsId(); + } + return result; +} + template <> TDuration ConvertValue(const ui32& value) { @@ -221,6 +256,26 @@ void DumpImpl(const T& t, IOutputStream& os) os << t; } +template <> +void DumpImpl(const TCertificate& value, IOutputStream& os) +{ + os << "{ " + << value.CertFile + << ", " + << value.CertPrivateKeyFile + << " }"; +} + +template <> +void DumpImpl(const TAliasMap& value, IOutputStream& os) +{ + os << "{ "; + for (const auto& [alias, fsId]: value) { + os << alias << ": " << fsId << ", "; + } + os << " }"; +} + } // namespace //////////////////////////////////////////////////////////////////////////////// diff --git a/cloud/filestore/libs/storage/core/config.h b/cloud/filestore/libs/storage/core/config.h index 9793aeb49b..d1a17152f4 100644 --- a/cloud/filestore/libs/storage/core/config.h +++ b/cloud/filestore/libs/storage/core/config.h @@ -226,6 +226,8 @@ class TStorageConfig TString GetBlobCompressionCodec() const; const NProto::TStorageConfig& GetStorageConfigProto() const; + + THashMap GetFilestoreAliases() const; }; } // namespace NCloud::NFileStore::NStorage diff --git a/cloud/filestore/libs/storage/service/service_actor_createsession.cpp b/cloud/filestore/libs/storage/service/service_actor_createsession.cpp index 33e1a1b664..a472dd1728 100644 --- a/cloud/filestore/libs/storage/service/service_actor_createsession.cpp +++ b/cloud/filestore/libs/storage/service/service_actor_createsession.cpp @@ -652,7 +652,11 @@ void TStorageServiceActor::HandleCreateSession( const auto* msg = ev->Get(); const auto& clientId = GetClientId(msg->Record); - const auto& fileSystemId = msg->Record.GetFileSystemId(); + TString fileSystemId = msg->Record.GetFileSystemId(); + auto it = StorageConfig->GetFilestoreAliases().find(fileSystemId); + if (it != StorageConfig->GetFilestoreAliases().end()) { + fileSystemId = it->second; + } const auto& checkpointId = msg->Record.GetCheckpointId(); auto originFqdn = GetOriginFqdn(msg->Record); diff --git a/cloud/filestore/libs/storage/service/service_ut.cpp b/cloud/filestore/libs/storage/service/service_ut.cpp index 16dfb7e16d..3192eac842 100644 --- a/cloud/filestore/libs/storage/service/service_ut.cpp +++ b/cloud/filestore/libs/storage/service/service_ut.cpp @@ -5213,6 +5213,72 @@ Y_UNIT_TEST_SUITE(TStorageServiceTest) createSessionResponse->GetErrorReason()); service.AssertDestroyFileStoreFailed(fsId, true); + Y_UNIT_TEST(ShouldUseAliasesForRequestsForwarding) + { + const TString originalFs = "test"; + const TString mirroredFs = "test-mirrored"; + + NProto::TStorageConfig::TFilestoreAliasEntry entry; + entry.SetAlias(mirroredFs); + entry.SetFsId(originalFs); + NProto::TStorageConfig::TFilestoreAliases aliases; + aliases.MutableEntries()->Add(std::move(entry)); + + NProto::TStorageConfig config; + config.MutableFilestoreAliases()->Swap(&aliases); + + TTestEnv env({}, config); + env.CreateSubDomain("nfs"); + + ui32 nodeIdx = env.CreateNode("nfs"); + + + TServiceClient service(env.GetRuntime(), nodeIdx); + service.CreateFileStore(originalFs, 1000); + service.CreateFileStore(mirroredFs, 1000); + + auto originalHeaders = service.InitSession(originalFs, "client"); + auto mirroredHeaders = service.InitSession(mirroredFs, "client"); + + // Create file in the original fs + service.CreateNode(originalHeaders, TCreateNodeArgs::File(RootNodeId, "testfile")); + + // Check that the file is visible in the mirrored fs + auto listNodesResponse = + service.ListNodes(mirroredHeaders, mirroredFs, RootNodeId)->Record; + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NamesSize()); + UNIT_ASSERT_VALUES_EQUAL(1, listNodesResponse.NodesSize()); + UNIT_ASSERT_VALUES_EQUAL("testfile", listNodesResponse.GetNames(0)); + + auto nodeId = listNodesResponse.GetNodes(0).GetId(); + + // write to the file in the mirrored fs + auto mirroredHandle = service.CreateHandle( + mirroredHeaders, + mirroredFs, + nodeId, + "", + TCreateHandleArgs::WRNLY)->Record.GetHandle(); + auto data = GenerateValidateData(256_KB); + service.WriteData(mirroredHeaders, mirroredFs, nodeId, mirroredHandle, 0, data); + + // validate that written data can be read from the original fs + auto originalHandle = service.CreateHandle( + originalHeaders, + originalFs, + nodeId, + "", + TCreateHandleArgs::RDNLY)->Record.GetHandle(); + auto readData = service.ReadData( + originalHeaders, + originalFs, + nodeId, + originalHandle, + 0, + 256_KB)->Record.GetBuffer(); + UNIT_ASSERT_VALUES_EQUAL(data, readData); + } + Y_UNIT_TEST(ShouldWriteCompactionMap) { TTestEnv env; diff --git a/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_actor.cpp b/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_actor.cpp index 6caced6b4a..8685e9fc0d 100644 --- a/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_actor.cpp +++ b/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_actor.cpp @@ -318,7 +318,12 @@ void TIndexTabletProxyActor::HandleRequest( const auto* msg = ev->Get(); - const TString& fileSystemId = GetFileSystemId(*msg); + TString fileSystemId = GetFileSystemId(*msg); + // Some filestore names can point to another filestore, set by storage config + auto it = Config->GetFilestoreAliases().find(fileSystemId); + if (it != Config->GetFilestoreAliases().end()) { + fileSystemId = it->second; + } TConnection& conn = CreateConnection(fileSystemId); switch (conn.State) { diff --git a/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_ut.cpp b/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_ut.cpp index 98fa3e1722..012eb58f4e 100644 --- a/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_ut.cpp +++ b/cloud/filestore/libs/storage/tablet_proxy/tablet_proxy_ut.cpp @@ -98,6 +98,62 @@ Y_UNIT_TEST_SUITE(TIndexTabletProxyTest) env.GetRuntime().DispatchEvents({}, TDuration::MilliSeconds(100)); tabletProxy.WaitReady("test"); } + + Y_UNIT_TEST(ShouldForwardRequestsByAliases) + { + const TString originalFs = "test"; + const TString mirroredFs = "test-mirrored"; + + NProto::TStorageConfig::TFilestoreAliasEntry entry; + entry.SetAlias(mirroredFs); + entry.SetFsId(originalFs); + NProto::TStorageConfig::TFilestoreAliases aliases; + aliases.MutableEntries()->Add(std::move(entry)); + NProto::TStorageConfig storageConfig; + storageConfig.MutableFilestoreAliases()->Swap(&aliases); + + TTestEnvConfig cfg{.StaticNodes = 1, .DynamicNodes = 2}; + TTestEnv env(cfg, storageConfig); + env.CreateSubDomain("nfs"); + + auto& runtime = env.GetRuntime(); + ui32 nodeIdx = env.CreateNode("nfs"); + + TSSProxyClient ssProxy(env.GetStorageConfig(), runtime, nodeIdx); + ssProxy.CreateFileStore(originalFs, 1000); + ssProxy.CreateFileStore(mirroredFs, 1000); + + TIndexTabletProxyClient tabletProxy(env.GetRuntime(), nodeIdx); + + TVector responseTabletIds; + bool responseSent = false; + + env.GetRuntime().SetEventFilter( + [&](TTestActorRuntimeBase&, TAutoPtr& event) + { + switch (event->GetTypeRewrite()) { + case TEvService::EvListNodesResponse: { + if (!responseSent) { + responseTabletIds.push_back(event->Sender); + responseSent = true; + } + break; + } + } + + return false; + }); + + tabletProxy.SendListNodesRequest(originalFs, RootNodeId); + tabletProxy.RecvListNodesResponse(); + responseSent = false; + tabletProxy.SendListNodesRequest(mirroredFs, RootNodeId); + tabletProxy.RecvListNodesResponse(); + + // Both responses should be sent by the same actor + UNIT_ASSERT_EQUAL(responseTabletIds.size(), 2); + UNIT_ASSERT_EQUAL(responseTabletIds[0], responseTabletIds[1]); + } } } // namespace NCloud::NFileStore::NStorage diff --git a/cloud/filestore/libs/storage/testlib/tablet_proxy_client.h b/cloud/filestore/libs/storage/testlib/tablet_proxy_client.h index 228bbecdcd..12a968a31f 100644 --- a/cloud/filestore/libs/storage/testlib/tablet_proxy_client.h +++ b/cloud/filestore/libs/storage/testlib/tablet_proxy_client.h @@ -66,6 +66,14 @@ class TIndexTabletProxyClient return request; } + auto CreateListNodesRequest(const TString& fileSystemId, ui64 nodeId) + { + auto request = std::make_unique(); + request->Record.SetFileSystemId(fileSystemId); + request->Record.SetNodeId(nodeId); + return request; + } + #define FILESTORE_DECLARE_METHOD(name, ns) \ template \ void Send##name##Request(Args&&... args) \