using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; 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 PoolSmokeTests : SmokeTestsBase { private InitParameters GetDefaultParams() { return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), NodeParams = [new(1, url, 100.0f)], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, Logger = null }; } [Fact] public async void NetworkMapTest() { var options = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); Assert.Null(error); var result = await pool.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 options = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); Assert.Null(error); var result = await pool.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 NodeInfoStatisticsTwoNodesTest() { var callbackText = string.Empty; var options = new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), NodeParams = [ new(1, url, 100.0f), new(2, url.Replace('0', '1'), 100.0f) ], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, Logger = null, Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" }; var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); var error = await pool.Dial(ctx).ConfigureAwait(true); Assert.Null(error); var result = await pool.GetNodeInfoAsync(default); var statistics = pool.Statistic(); Assert.False(string.IsNullOrEmpty(callbackText)); Assert.Contains(" took ", callbackText, StringComparison.Ordinal); } [Fact] public async void NodeInfoStatisticsTest() { var options = GetDefaultParams(); var callbackText = string.Empty; options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); var error = await pool.Dial(ctx).ConfigureAwait(true); Assert.Null(error); var result = await pool.GetNodeInfoAsync(default); Assert.False(string.IsNullOrEmpty(callbackText)); Assert.Contains(" took ", callbackText, StringComparison.Ordinal); } [Fact] public async void GetSessionTest() { var options = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); var prm = new PrmSessionCreate(100); var token = await pool.CreateSessionAsync(prm, default).ConfigureAwait(true); var ownerHash = Base58.Decode(OwnerId!.Value); Assert.NotNull(token); Assert.NotEqual(Guid.Empty, token.Id); Assert.Equal(33, token.SessionKey.Length); } [Fact] public async void CreateObjectWithSessionToken() { var options = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); var token = await pool.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 pool.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 pool.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var @object = await pool.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 Cleanup(pool); } [Fact] public async void FilterTest() { var options = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.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 pool.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); var ecdsaKey = keyString.LoadWif(); var networkInfo = await pool.GetNetmapSnapshotAsync(default); await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); var checkSum = CheckSum.CreateCheckSum(bytes); await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); await CheckFilter(pool, containerId, new FilterByPhysicallyStored()); } private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter) { var resultObjectsCount = 0; PrmObjectSearch searchParam = new(containerId, null, [], filter); await foreach (var objId in pool.SearchObjectsAsync(searchParam, default)) { resultObjectsCount++; var objHeader = await pool.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 = GetDefaultParams(); options.Callback = new((cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); var ctx = new CallContext(TimeSpan.Zero); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var createdContainer = await pool.PutContainerAsync(createContainerParam, ctx); var container = await pool.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 pool.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 pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) { hasObject = true; var res = await pool.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 pool.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(pool); await foreach (var _ in pool.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 options = GetDefaultParams(); var pool = new Pool(options); options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(pool); 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 pool.PutContainerAsync(createContainerParam, ctx); var containerInfo = await pool.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 pool.PutObjectAsync(param, new CallContext(TimeSpan.Zero)).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; var objs = pool.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default); await foreach (var objId in objs) { hasObject = true; var res = await pool.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 pool.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(pool); await foreach (var _ in pool.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 = GetDefaultParams(); var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.PutContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); // ctx.Interceptors.Add(new CallbackInterceptor()); var container = await pool.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 pool.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; var res = await pool.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 pool.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(pool, containerId, new FilterByRootObject()); await Cleanup(pool); await foreach (var cid in pool.ListContainersAsync(default, default)) { Assert.Fail($"Container {cid.GetValue()} exist"); } } }