diff --git a/cloud/filestore/apps/client/lib/command.cpp b/cloud/filestore/apps/client/lib/command.cpp index 8f9f58e71b..25664d137d 100644 --- a/cloud/filestore/apps/client/lib/command.cpp +++ b/cloud/filestore/apps/client/lib/command.cpp @@ -532,6 +532,20 @@ NProto::TListNodesResponse TFileStoreCommand::ListAll( return fullResult; } +TString TFileStoreCommand::ReadLink(ISession& session, ui64 nodeId) +{ + auto request = CreateRequest(); + request->SetNodeId(nodeId); + + auto response = WaitFor(session.ReadLink( + PrepareCallContext(), + std::move(request))); + + CheckResponse(response); + + return response.GetSymLink(); +} + //////////////////////////////////////////////////////////////////////////////// TEndpointCommand::TEndpointCommand() diff --git a/cloud/filestore/apps/client/lib/command.h b/cloud/filestore/apps/client/lib/command.h index 2a7e26a292..e79d9fe5ea 100644 --- a/cloud/filestore/apps/client/lib/command.h +++ b/cloud/filestore/apps/client/lib/command.h @@ -198,6 +198,7 @@ class TFileStoreCommand const TString& fsId, ui64 parentId, bool disableMultiTabletForwarding); + TString ReadLink(ISession& session, ui64 nodeId); class TSessionGuard final { diff --git a/cloud/filestore/apps/client/lib/diff.cpp b/cloud/filestore/apps/client/lib/diff.cpp index 1f8c63934f..5efd1be3b6 100644 --- a/cloud/filestore/apps/client/lib/diff.cpp +++ b/cloud/filestore/apps/client/lib/diff.cpp @@ -183,6 +183,17 @@ class TDiffCommand final DumpLine(RDiff() + "DATA: ", rhash); } } + + if (lattr.GetType() == NProto::E_LINK_NODE + && rattr.GetType() == NProto::E_LINK_NODE) + { + const auto lcontent = ReadLink(lsession, lattr.GetId()); + const auto rcontent = ReadLink(rsession, rattr.GetId()); + if (lcontent != rcontent) { + DumpLine(LDiff() + "LINK: ", lcontent); + DumpLine(RDiff() + "LINK: ", rcontent); + } + } } bool Execute() override diff --git a/cloud/filestore/apps/client/lib/factory.cpp b/cloud/filestore/apps/client/lib/factory.cpp index f69ef54b9e..e807c740e5 100644 --- a/cloud/filestore/apps/client/lib/factory.cpp +++ b/cloud/filestore/apps/client/lib/factory.cpp @@ -17,6 +17,7 @@ TCommandPtr NewKickEndpointCommand(); TCommandPtr NewListClusterNodesCommand(); TCommandPtr NewListEndpointsCommand(); TCommandPtr NewListFileStoresCommand(); +TCommandPtr NewLnCommand(); TCommandPtr NewLsCommand(); TCommandPtr NewMkDirCommand(); TCommandPtr NewMountCommand(); @@ -61,6 +62,7 @@ static const TMap Commands = { { "listclusternodes", NewListClusterNodesCommand }, { "listendpoints", NewListEndpointsCommand }, { "listfilestores", NewListFileStoresCommand }, + { "ln", NewLnCommand }, { "ls", NewLsCommand }, { "mkdir", NewMkDirCommand }, { "mount", NewMountCommand }, diff --git a/cloud/filestore/apps/client/lib/ln.cpp b/cloud/filestore/apps/client/lib/ln.cpp new file mode 100644 index 0000000000..1c114beafe --- /dev/null +++ b/cloud/filestore/apps/client/lib/ln.cpp @@ -0,0 +1,67 @@ +#include "command.h" + +#include + +#include +#include + +namespace NCloud::NFileStore::NClient { + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +class TLnCommand final + : public TFileStoreCommand +{ +private: + TString Path; + TString SymLink; + +public: + TLnCommand() + { + Opts.AddLongOption("path") + .Required() + .RequiredArgument("PATH") + .StoreResult(&Path); + + Opts.AddLongOption("symlink") + .Required() + .RequiredArgument("PATH") + .StoreResult(&SymLink); + } + + bool Execute() override + { + auto sessionGuard = CreateSession(); + auto& session = sessionGuard.AccessSession(); + + auto resolved = ResolvePath(session, Path, true); + + Y_ENSURE(resolved.size() >= 2, "root node can't be ln target"); + + auto request = CreateRequest(); + request->SetNodeId(resolved[resolved.size() - 2].Node.GetId()); + request->SetName(TString(resolved.back().Name)); + request->MutableSymLink()->SetTargetPath(SymLink); + + auto response = WaitFor(session.CreateNode( + PrepareCallContext(), + std::move(request))); + + CheckResponse(response); + return true; + } +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +TCommandPtr NewLnCommand() +{ + return std::make_shared(); +} + +} // namespace NCloud::NFileStore::NClient diff --git a/cloud/filestore/apps/client/lib/ya.make b/cloud/filestore/apps/client/lib/ya.make index 3ad790fb20..23207f296b 100644 --- a/cloud/filestore/apps/client/lib/ya.make +++ b/cloud/filestore/apps/client/lib/ya.make @@ -20,6 +20,7 @@ SRCS( list_cluster_nodes.cpp list_endpoints.cpp list_filestores.cpp + ln.cpp ls.cpp mkdir.cpp mount.cpp diff --git a/cloud/filestore/libs/storage/api/service.h b/cloud/filestore/libs/storage/api/service.h index 0916670b59..bdcaa8129c 100644 --- a/cloud/filestore/libs/storage/api/service.h +++ b/cloud/filestore/libs/storage/api/service.h @@ -31,9 +31,6 @@ namespace NCloud::NFileStore::NStorage { xxx(DestroyCheckpoint, __VA_ARGS__) \ \ xxx(ResolvePath, __VA_ARGS__) \ - xxx(UnlinkNode, __VA_ARGS__) \ - xxx(RenameNode, __VA_ARGS__) \ - xxx(ReadLink, __VA_ARGS__) \ \ // FILESTORE_SERVICE_REQUESTS_FWD @@ -44,6 +41,10 @@ namespace NCloud::NFileStore::NStorage { xxx(SetNodeXAttr, __VA_ARGS__) \ xxx(ListNodeXAttr, __VA_ARGS__) \ xxx(RemoveNodeXAttr, __VA_ARGS__) \ + \ + xxx(UnlinkNode, __VA_ARGS__) \ + xxx(RenameNode, __VA_ARGS__) \ + xxx(ReadLink, __VA_ARGS__) \ // FILESTORE_SERVICE_REQUESTS_FWD_TO_SHARD_BY_NODE_ID #define FILESTORE_SERVICE_REQUESTS_FWD_TO_SHARD_BY_HANDLE(xxx, ...) \ diff --git a/cloud/filestore/tests/client_sharded_dir/canondata/test.test_nonsharded_vs_sharded_fs/results.txt b/cloud/filestore/tests/client_sharded_dir/canondata/test.test_nonsharded_vs_sharded_fs/results.txt index d3ea73b380..434759e131 100644 --- a/cloud/filestore/tests/client_sharded_dir/canondata/test.test_nonsharded_vs_sharded_fs/results.txt +++ b/cloud/filestore/tests/client_sharded_dir/canondata/test.test_nonsharded_vs_sharded_fs/results.txt @@ -22,4 +22,5 @@ "ShardFileSystemId": "masked_for_test_stability", "Name": "a1" } -] \ No newline at end of file +]- LINK: /does/not/matter/2 ++ LINK: /does/not/matter/3 diff --git a/cloud/filestore/tests/client_sharded_dir/test.py b/cloud/filestore/tests/client_sharded_dir/test.py index 46030c9691..759d15e6dd 100644 --- a/cloud/filestore/tests/client_sharded_dir/test.py +++ b/cloud/filestore/tests/client_sharded_dir/test.py @@ -52,23 +52,30 @@ def __write_some_data(client, fs_id, path, data): client.write(fs_id, path, "--data", data_file) +_DIR = 1 +_FILE = 2 +_SYMLINK = 3 + + class FsItem: - def __init__(self, path, is_dir, data): + def __init__(self, path, node_type, data): self.path = path - self.is_dir = is_dir + self.node_type = node_type self.data = data def __fill_fs(client, fs_id, items): for item in items: - if item.is_dir: + if item.node_type == _DIR: client.mkdir(fs_id, item.path) - else: + elif item.node_type == _FILE: if item.data is not None: __write_some_data(client, fs_id, item.path, item.data) else: client.touch(fs_id, item.path) + else: + client.ln(fs_id, item.path, "--symlink", item.data) def test_nonsharded_vs_sharded_fs(): @@ -87,10 +94,13 @@ def test_nonsharded_vs_sharded_fs(): 3 * int(SHARD_SIZE / BLOCK_SIZE)) def _d(path): - return FsItem(path, True, None) + return FsItem(path, _DIR, None) def _f(path, data=None): - return FsItem(path, False, data) + return FsItem(path, _FILE, data) + + def _l(path, symlink): + return FsItem(path, _SYMLINK, symlink) items = [ _d("/a0"), @@ -117,12 +127,23 @@ def _f(path, data=None): _f("/a1/b2/f14.txt", "zzzzz4"), _d("/a1/b2/c1"), _f("/a1/b2/f15.txt", "ZZZZZZZZZZZ"), + _l("/a1/b2/l1", "/does/not/matter"), + _f("/a1/b2/f16.txt", "ZZZZZZZZZZZ2"), _d("/a1/b3"), ] __fill_fs(client, "fs0", items) __fill_fs(client, "fs1", items) + # checking that mv, rm and ln work properly + client.mv("fs0", "/a0/b0/c0/d0/f9.txt", "/a0/b0/c0/d0/f9_moved.txt") + client.mv("fs1", "/a0/b0/c0/d0/f9.txt", "/a0/b0/c0/d0/f9_moved.txt") + client.rm("fs0", "/a1/b2/f16.txt") + client.rm("fs1", "/a1/b2/f16.txt") + # checking that readlink works (indirectly - via diff) + client.ln("fs0", "/a1/b2/l2", "--symlink", "/does/not/matter/2") + client.ln("fs1", "/a1/b2/l2", "--symlink", "/does/not/matter/3") + out = __exec_ls(client, "fs0", "/", "--disable-multitablet-forwarding") out += __exec_ls(client, "fs1", "/", "--disable-multitablet-forwarding") out += client.diff("fs0", "fs1") diff --git a/cloud/filestore/tests/python/lib/client.py b/cloud/filestore/tests/python/lib/client.py index 2b90463fc6..fa216f853a 100644 --- a/cloud/filestore/tests/python/lib/client.py +++ b/cloud/filestore/tests/python/lib/client.py @@ -354,6 +354,10 @@ def touch(self, cmd): def rm(self, cmd): return common.execute(cmd, env=self.__env, check_exit_code=self.__check_exit_code).stdout + @standard_command("ln") + def ln(self, cmd): + return common.execute(cmd, env=self.__env, check_exit_code=self.__check_exit_code).stdout + def create_endpoint(client, filesystem, socket_path, socket_prefix, endpoint_storage_dir, mount_seqno=0, readonly=False): _uid = str(uuid.uuid4())