using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.SmokeTests; namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class MultithreadSmokeClientTests : SmokeTestsBase { [Fact] public async void AccountTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetBalanceAsync(default); Assert.NotNull(result); Assert.True(result.Value == 0); } [Fact] public async void NetworkMapTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNetmapSnapshotAsync(default); Assert.True(result.Epoch > 0); Assert.Single(result.NodeInfoCollection); var item = result.NodeInfoCollection[0]; Assert.Equal(2, item.Version.Major); Assert.Equal(13, item.Version.Minor); Assert.Equal(NodeState.Online, item.State); Assert.True(item.PublicKey.Length > 0); Assert.Single(item.Addresses); Assert.Equal(9, item.Attributes.Count); } [Fact] public async void NodeInfoTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.Single(result.Addresses); Assert.Equal(9, result.Attributes.Count); } [Fact] public async void NodeInfoStatisticsTest() { var options = ClientOptions; var callbackContent = string.Empty; options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.NotEmpty(callbackContent); } [Fact] public async void GetSessionTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var token = await client.CreateSessionAsync(new(100), default); Assert.NotNull(token); Assert.NotEqual(Guid.Empty, token.Id); Assert.Equal(33, token.SessionKey.Length); } [Fact] public async void CreateObjectWithSessionToken() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); var containerId = await client.PutContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), sessionToken: token); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) .ConfigureAwait(true); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client).ConfigureAwait(true); } [Fact] public async void FilterTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.PutContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; var ParentHeader = new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular) { PayloadLength = 3 }; var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], new FrostFsSplit())); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); var ecdsaKey = keyString.LoadWif(); var networkInfo = await client.GetNetmapSnapshotAsync(default); await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); var checkSum = CheckSum.CreateCheckSum(bytes); await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); await CheckFilter(client, containerId, new FilterByPhysicallyStored()); } private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) { var resultObjectsCount = 0; PrmObjectSearch searchParam = new(containerId, null, [], filter); await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) { resultObjectsCount++; var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); } Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); } [Theory] [InlineData(1)] [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { bool callbackInvoked = false; var options = ClientOptions; options.Value.Callback = new((cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }); var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var createdContainer = await client.PutContainerAsync(createContainerParam, ctx); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")])); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void PatchTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[1024]; for (int i = 0; i < 1024; i++) { bytes[i] = 31; } var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")])); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var patch = new byte[16]; for (int i = 0; i < 16; i++) { patch[i] = 32; } var range = new FrostFsRange(8, (ulong)patch.Length); var patchParams = new PrmObjectPatch( new FrostFsAddress(createdContainer, objectId), payload: new MemoryStream(patch), maxChunkLength: 32, range: range); var newIbjId = await client.PatchObjectAsync(patchParams, default); var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } for (int i = 0; i < (int)range.Offset; i++) Assert.Equal(downloadedBytes[i], bytes[i]); var rangeEnd = range.Offset + range.Length; for (int i = (int)range.Offset; i < (int)rangeEnd; i++) Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); for (int i = (int)rangeEnd; i < bytes.Length; i++) Assert.Equal(downloadedBytes[i], bytes[i]); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void RangeTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[256]; for (int i = 0; i < 256; i++) { bytes[i] = (byte)i; } var param = new PrmObjectPut( new FrostFsObjectHeader(containerId: createdContainer, type: FrostFsObjectType.Regular)); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); var rangeReader = await client.GetRangeAsync(rangeParam, default); var downloadedBytes = new byte[rangeParam.Range.Length]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await rangeReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(100, 64)), SHA256.HashData(downloadedBytes)); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void RangeHashTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[256]; for (int i = 0; i < 256; i++) { bytes[i] = (byte)i; } var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular)); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); var hashes = await client.GetRangeHashAsync(rangeParam, default); foreach (var hash in hashes) { var x = hash[..32].ToArray(); } await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Theory] [InlineData(1)] [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(client); var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); var container = await client.PutContainerAsync(createContainerParam, ctx); var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), sessionToken: token); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync( new PrmObjectSearch(container, token, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Theory] [InlineData(1)] [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB [InlineData(64 * 1024 * 1024 - 1)] [InlineData(64 * 1024 * 1024 + 1)] [InlineData(2 * 64 * 1024 * 1024 + 256)] [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { var options = ClientOptions; options.Value.Interceptors.Add(new CallbackInterceptor()); var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.PutContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); Assert.NotNull(container); byte[] bytes = GetRandomBytes(objectSize); var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), payload: new MemoryStream(bytes)); var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes[0].Key); Assert.Equal("test", objHeader.Attributes[0].Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await CheckFilter(client, containerId, new FilterByRootObject()); await Cleanup(client); var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); IAsyncEnumerator? enumerator = null; do { if (deadline <= DateTime.UtcNow) { Assert.Fail("Containers exist"); break; } enumerator = client.ListContainersAsync(default, default).GetAsyncEnumerator(); await Task.Delay(500); } while (await enumerator!.MoveNextAsync()); } [Fact] public async void NodeInfoCallbackAndInterceptorTest() { bool callbackInvoked = false; bool intercepterInvoked = false; var options = ClientOptions; options.Value.Callback = (cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }; options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.True(callbackInvoked); Assert.True(intercepterInvoked); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.Single(result.Addresses); Assert.Equal(9, result.Attributes.Count); } }