From ae67b12313b0e8b5ee4ee4aa2879fc48c2aaf235 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 1 Jul 2024 11:56:47 +0300 Subject: [PATCH] [#14] Add interceptors Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 196 +++++++++++++----- .../Interceptors/MetricsInterceptor.cs | 73 +++++++ .../Interfaces/IFrostFSClient.cs | 29 ++- .../Mappers/Session/Session.cs | 6 +- .../Services/ContainerServiceProvider.cs | 4 - .../Services/ObjectServiceProvider.cs | 160 +++----------- .../Services/ObjectTools.cs | 104 ++++++++++ .../Tools/ClientEnvironment.cs | 14 +- src/FrostFS.SDK.ModelsV2/CallStatistics.cs | 7 + src/FrostFS.SDK.ModelsV2/Context.cs | 13 ++ .../FrostFS.SDK.ModelsV2.csproj | 4 + .../FrostFS.SDK.ProtosV2.csproj | 2 +- src/FrostFS.SDK.Tests/ClientTestLive.cs | 108 ++++++---- .../ContainerServiceBase.cs | 28 --- .../DeleteContainerMock.cs | 69 ------ .../ContainerServiceMocks/GetContainerMock.cs | 163 --------------- .../{ClientTest.cs => ContainerTest.cs} | 58 ++++-- .../ContainerServiceBase.cs | 78 +++++++ .../DeleteContainerMock.cs | 54 +++++ .../GetContainerMock copy.cs | 13 ++ .../ContainerServiceMocks/GetContainerMock.cs | 58 ++++++ .../ContainerServiceMocks/PutContainerMock.cs | 2 +- .../{ => Mocks}/NetmapMock.cs | 2 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 101 +++++++++ src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 57 +++++ src/FrostFS.SDK.Tests/ObjectMock.cs | 14 -- src/FrostFS.SDK.Tests/ObjectTest.cs | 66 ++++++ src/FrostFS.SDK.Tests/SessionMock.cs | 14 -- 28 files changed, 943 insertions(+), 554 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs create mode 100644 src/FrostFS.SDK.ModelsV2/CallStatistics.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs rename src/FrostFS.SDK.Tests/{ClientTest.cs => ContainerTest.cs} (57%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs rename src/FrostFS.SDK.Tests/{ => Mocks}/ContainerServiceMocks/PutContainerMock.cs (97%) rename src/FrostFS.SDK.Tests/{ => Mocks}/NetmapMock.cs (78%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/SessionMock.cs delete mode 100644 src/FrostFS.SDK.Tests/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/ObjectTest.cs delete mode 100644 src/FrostFS.SDK.Tests/SessionMock.cs diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 2b87c46..ca1acea 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -7,11 +7,13 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.Session; using Grpc.Core; +using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Http; +using System.Security.Cryptography; using System.Threading.Tasks; using Version = FrostFS.SDK.ModelsV2.Version; @@ -20,10 +22,15 @@ namespace FrostFS.SDK.ClientV2; public class Client : IFrostFSClient { private bool isDisposed; + + internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; } + internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; } + internal SessionService.SessionServiceClient? SessionServiceClient { get; set; } + internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } internal ClientEnvironment ClientCtx { get; set; } - public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions channelOptions) + public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { return new Client(clientOptions, channelOptions); } @@ -32,7 +39,7 @@ public class Client : IFrostFSClient /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. /// /// Global setting for client - /// Setting for gRPC cjannel + /// Setting for gRPC channel /// ContainerService.ContainerServiceClient implementation /// Netmap.NetmapService.NetmapServiceClient implementation /// Session.SessionService.SessionServiceClient implementation @@ -61,18 +68,19 @@ public class Client : IFrostFSClient OwnerId.FromKey(ecdsaKey); ClientCtx = new ClientEnvironment( + this, key: ecdsaKey, owner: OwnerId.FromKey(ecdsaKey), channel: InitGrpcChannel(settings.Value.Host, channelOptions), version: new Version(2, 13)); - ClientCtx.ContainerService = new ContainerServiceProvider(containerService, ClientCtx); - ClientCtx.NetmapService = new NetmapServiceProvider(netmapService, ClientCtx); - ClientCtx.SessionService = new SessionServiceProvider(sessionService, ClientCtx); - ClientCtx.ObjectService = new ObjectServiceProvider(objectService, ClientCtx); + ContainerServiceClient = containerService; + NetmapServiceClient = netmapService; + SessionServiceClient = sessionService; + ObjectServiceClient = objectService; } - private Client(IOptions options, GrpcChannelOptions channelOptions) + private Client(IOptions options, GrpcChannelOptions? channelOptions) { var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); @@ -83,16 +91,12 @@ public class Client : IFrostFSClient var channel = InitGrpcChannel(clientSettings.Host, channelOptions); ClientCtx = new ClientEnvironment( + this, key: ecdsaKey, owner: OwnerId.FromKey(ecdsaKey), channel: channel, version: new Version(2, 13)); - ClientCtx.ContainerService = new ContainerServiceProvider(new ContainerService.ContainerServiceClient(channel), ClientCtx); - ClientCtx.NetmapService = new NetmapServiceProvider(new NetmapService.NetmapServiceClient(channel), ClientCtx); - ClientCtx.SessionService = new SessionServiceProvider(new SessionService.SessionServiceClient(channel), ClientCtx); - ClientCtx.ObjectService = new ObjectServiceProvider(new ObjectService.ObjectServiceClient(channel), ClientCtx); - CheckFrostFsVersionSupport(); } @@ -111,72 +115,81 @@ public class Client : IFrostFSClient } } - public GrpcChannel Channel => ClientCtx.Channel; - + #region ContainerImplementation public Task GetContainerAsync(ContainerId containerId, Context? ctx = null) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!); + var service = GetContainerService(ref ctx); + return service.GetContainerAsync(containerId, ctx!); } public IAsyncEnumerable ListContainersAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.ListContainersAsync(ctx!); + var service = GetContainerService(ref ctx); + return service.ListContainersAsync(ctx!); } public Task CreateContainerAsync(ModelsV2.Container container, Context? ctx = null) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.CreateContainerAsync(container, ctx!); + var service = GetContainerService(ref ctx); + return service.CreateContainerAsync(container, ctx!); } public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.DeleteContainerAsync(containerId, ctx!); + var service = GetContainerService(ref ctx); + return service.DeleteContainerAsync(containerId, ctx!); } + #endregion + #region NetworkImplementation public Task GetNetmapSnapshotAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!); + var service = GetNetmapService(ref ctx); + return service.GetNetmapSnapshotAsync(ctx!); } public Task GetNodeInfoAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + var service = GetNetmapService(ref ctx); + return service.GetLocalNodeInfoAsync(ctx!); } + public Task GetNetworkSettingsAsync(Context? ctx = default) + { + var service = GetNetmapService(ref ctx); + return service.GetNetworkSettingsAsync(ctx!); + } + #endregion + + #region ObjectImplementation public Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.GetObjectHeadAsync(containerId, objectId, ctx!); } public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.GetObjectAsync(containerId, objectId, ctx!); } public Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!); + var service = GetObjectService(ref ctx); + return service.PutObjectAsync(putObjectParameters, ctx!); } public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.PutSingleObjectAsync(obj, ctx!); + var service = GetObjectService(ref ctx); + return service.PutSingleObjectAsync(obj, ctx!); } public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.DeleteObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.DeleteObjectAsync(containerId, objectId, ctx!); } public IAsyncEnumerable SearchObjectsAsync( @@ -184,19 +197,38 @@ public class Client : IFrostFSClient IEnumerable filters, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.SearchObjectsAsync(containerId, filters, ctx!); + var service = GetObjectService(ref ctx); + return service.SearchObjectsAsync(containerId, filters, ctx!); + } + #endregion + + #region SessionImplementation + public async Task CreateSessionAsync(ulong expiration, Context? ctx = null) + { + var session = await CreateSessionInternalAsync(expiration, ctx); + var token = session.Serialize(); + + return new ModelsV2.SessionToken([], token); } + public Task CreateSessionInternalAsync(ulong expiration, Context? ctx = null) + { + var service = GetSessionService(ref ctx); + return service.CreateSessionAsync(expiration, ctx!); + } + #endregion + + #region ToolsImplementation public ObjectId CalculateObjectId(ObjectHeader header) - { - return ClientCtx.ObjectService!.CalculateObjectId(header); + { + return new ObjectTools(ClientCtx).CalculateObjectId(header); } + #endregion private async void CheckFrostFsVersionSupport(Context? ctx = default) { - ValidateEnvironment(ref ctx); - var localNodeInfo = await ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + var service = GetNetmapService(ref ctx); + var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!); if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { @@ -206,15 +238,76 @@ public class Client : IFrostFSClient } } - private void ValidateEnvironment(ref Context? ctx) + private CallInvoker? SetupEnvironment(ref Context? ctx) { if (isDisposed) throw new Exception("Client is disposed."); - if (ClientCtx == null || !ClientCtx.Initialized) - throw new Exception("Client is not initialized."); - ctx ??= new Context(); + + CallInvoker? callInvoker = null; + if (ctx.Interceptors != null && ctx.Interceptors.Count > 0) + { + foreach (var interceptor in ctx.Interceptors) + { + callInvoker = AddInvoker(callInvoker, interceptor); + } + } + + if (ctx.Callback != null) + callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Callback)); + + return callInvoker; + + CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor) + { + if (callInvoker == null) + callInvoker = ClientCtx.Channel.Intercept(interceptor); + else + callInvoker.Intercept(interceptor); + + return callInvoker; + } + } + + private NetmapServiceProvider GetNetmapService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = NetmapServiceClient ?? (callInvoker != null + ? new NetmapService.NetmapServiceClient(callInvoker) + : new NetmapService.NetmapServiceClient(ClientCtx.Channel)); + + return new NetmapServiceProvider(client, ClientCtx); + } + + private SessionServiceProvider GetSessionService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = SessionServiceClient ?? (callInvoker != null + ? new SessionService.SessionServiceClient(callInvoker) + : new SessionService.SessionServiceClient(ClientCtx.Channel)); + + return new SessionServiceProvider(client, ClientCtx); + } + + private ContainerServiceProvider GetContainerService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = ContainerServiceClient ?? (callInvoker != null + ? new ContainerService.ContainerServiceClient(callInvoker) + : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); + + return new ContainerServiceProvider(client, ClientCtx); + } + + private ObjectServiceProvider GetObjectService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = ObjectServiceClient ?? (callInvoker != null + ? new ObjectService.ObjectServiceClient(callInvoker) + : new ObjectService.ObjectServiceClient(ClientCtx.Channel)); + + return new ObjectServiceProvider(client, ClientCtx); } private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) @@ -230,21 +323,14 @@ public class Client : IFrostFSClient throw new ArgumentException(msg); } - ChannelCredentials grpcCredentials = uri.Scheme switch - { - "https" => ChannelCredentials.SecureSsl, - "http" => ChannelCredentials.Insecure, - _ => throw new ArgumentException($"Host '{host}' has invalid URI scheme: '{uri.Scheme}'.") - }; - if (channelOptions != null) { return GrpcChannel.ForAddress(uri, channelOptions); } - + + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions { - Credentials = grpcCredentials, HttpHandler = new HttpClientHandler() }); } diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs new file mode 100644 index 0000000..c395629 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs @@ -0,0 +1,73 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using FrostFS.SDK.ModelsV2; +using Grpc.Core; +using Grpc.Core.Interceptors; + +namespace FrostFS.SDK.ClientV2; + +public class MetricsInterceptor(Action callback) : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + public override AsyncClientStreamingCall AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation) + { + var call = continuation(context); + + return new AsyncClientStreamingCall( + call.RequestStream, + HandleStreamResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); + + return response; + } + + private async Task HandleStreamResponse(AsyncClientStreamingCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); + + return response; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 4234a1d..df5bcce 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -4,12 +4,24 @@ using System.Threading.Tasks; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; -using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable { + #region Network + Task GetNetmapSnapshotAsync(Context? context = default); + + Task GetNodeInfoAsync(Context? context = default); + + Task GetNetworkSettingsAsync(Context? context = default); + #endregion + + #region Session + Task CreateSessionAsync(ulong expiration, Context? context = default); + #endregion + + #region Container Task GetContainerAsync(ContainerId containerId, Context? context = default); IAsyncEnumerable ListContainersAsync(Context? context = default); @@ -17,7 +29,9 @@ public interface IFrostFSClient : IDisposable Task CreateContainerAsync(ModelsV2.Container container, Context? context = default); Task DeleteContainerAsync(ContainerId containerId, Context? context = default); - + #endregion + + #region Object Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); @@ -29,13 +43,10 @@ public interface IFrostFSClient : IDisposable Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); IAsyncEnumerable SearchObjectsAsync(ContainerId cid, IEnumerable filters, Context? context = default); - - Task GetNetmapSnapshotAsync(Context? context = default); - - Task GetNodeInfoAsync(Context? context = default); - + #endregion + + #region Tools ObjectId CalculateObjectId(ObjectHeader header); - - GrpcChannel Channel { get; } + #endregion } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs index 402f7e1..4b85759 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -2,17 +2,17 @@ using System; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.ClientV2; public static class SessionMapper { - internal static string SerializeSessionToken(this Session.SessionToken token) + internal static byte[] Serialize(this Session.SessionToken token) { byte[] bytes = new byte[token.CalculateSize()]; CodedOutputStream stream = new(bytes); token.WriteTo(stream); - return Convert.ToBase64String(bytes); + return bytes; } internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes) diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index b2d72ee..b2aadad 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -80,10 +80,6 @@ internal class ContainerServiceProvider : ContextAccessor request.Sign(Context.Key); var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); - - //var response = await Context.InvokeAsyncUnaryWithMetrics(() => - // containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken), - // nameof(containerServiceClient.PutAsync)); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 01999a5..2439c6e 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Threading.Tasks; using Google.Protobuf; -using System.Threading.Tasks; - using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -17,15 +15,9 @@ using FrostFS.SDK.ClientV2.Extensions; namespace FrostFS.SDK.ClientV2; -internal class ObjectServiceProvider : ContextAccessor +internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx) { - private readonly ObjectService.ObjectServiceClient objectServiceClient; - - internal ObjectServiceProvider(ObjectService.ObjectServiceClient objectServiceClient, ClientEnvironment context) - : base (context) - { - this.objectServiceClient = objectServiceClient; - } + readonly ObjectTools tools = new(ctx); internal async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx) { @@ -44,7 +36,7 @@ internal class ObjectServiceProvider : ContextAccessor request.AddMetaHeader(); request.Sign(Context.Key); - var response = await objectServiceClient!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -97,45 +89,38 @@ internal class ObjectServiceProvider : ContextAccessor return PutStreamObject(parameters, ctx); } - internal async Task PutSingleObjectAsync(ModelsV2.Object @object, Context ctx) + internal async Task PutSingleObjectAsync(ModelsV2.Object modelObject, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); - var obj = CreateObject(@object); + var grpcObject = tools.CreateObject(modelObject); var request = new PutSingleRequest { Body = new PutSingleRequest.Types.Body() { - Object = obj + Object = grpcObject } }; request.AddMetaHeader(); request.AddObjectSessionToken( sessionToken, - obj.Header.ContainerId, - obj.ObjectId, + grpcObject.Header.ContainerId, + grpcObject.ObjectId, ObjectSessionContext.Types.Verb.Put, Context.Key ); request.Sign(Context.Key); - var response = await objectServiceClient!.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); - return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); + return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } - - internal ObjectId CalculateObjectId(ObjectHeader header) - { - var grpcHeader = CreateHeader(header, []); - - return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); - } - + internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { var request = new DeleteRequest @@ -153,7 +138,7 @@ internal class ObjectServiceProvider : ContextAccessor request.AddMetaHeader(); request.Sign(Context.Key); - var response = await objectServiceClient!.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); } @@ -195,7 +180,7 @@ internal class ObjectServiceProvider : ContextAccessor List sentObjectIds = []; ModelsV2.Object? currentObject; - var networkSettings = await Context.NetmapService!.GetNetworkSettingsAsync(ctx); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); var partSize = (int)networkSettings.MaxObjectSize; var buffer = new byte[partSize]; @@ -215,7 +200,6 @@ internal class ObjectServiceProvider : ContextAccessor largeObject.AppendBlock(buffer, bytesCount); currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) - .AddAttributes(header.Attributes) .SetSplit(split); if (largeObject.PayloadLength == fullLength) @@ -228,6 +212,7 @@ internal class ObjectServiceProvider : ContextAccessor if (sentObjectIds.Count != 0) { + largeObject.AddAttributes(parameters.Header!.Attributes); largeObject.CalculateHash(); currentObject.SetParent(largeObject); @@ -238,11 +223,14 @@ internal class ObjectServiceProvider : ContextAccessor var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) .AddChildren(sentObjectIds); + linkObject.Header.Attributes.Clear(); + _ = await PutSingleObjectAsync(linkObject, ctx); - return CalculateObjectId(largeObject.Header); + return tools.CalculateObjectId(largeObject.Header); } + currentObject.AddAttributes(parameters.Header!.Attributes); return await PutSingleObjectAsync(currentObject, ctx); } @@ -257,10 +245,7 @@ internal class ObjectServiceProvider : ContextAccessor hdr.OwnerId = Context.Owner.ToGrpcMessage(); hdr.Version = Context.Version.ToGrpcMessage(); - var oid = new ObjectID - { - Value = hdr.Sha256() - }; + var oid = new ObjectID { Value = hdr.Sha256() }; var request = new PutRequest { @@ -269,7 +254,7 @@ internal class ObjectServiceProvider : ContextAccessor Init = new PutRequest.Types.Body.Types.Init { Header = hdr - }, + } } }; @@ -314,13 +299,12 @@ internal class ObjectServiceProvider : ContextAccessor { var reader = GetObjectInit(request, ctx); - var obj = await reader.ReadHeader(); + var grpcObject = await reader.ReadHeader(); + var modelObject = grpcObject.ToModel(); + + modelObject.ObjectReader = reader; - var @object = obj.ToModel(); - - @object.ObjectReader = reader; - - return @object; + return modelObject; } private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) @@ -328,7 +312,7 @@ internal class ObjectServiceProvider : ContextAccessor if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); - var call = objectServiceClient!.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); return new ObjectReader(call); } @@ -340,7 +324,7 @@ internal class ObjectServiceProvider : ContextAccessor throw new ArgumentNullException(nameof(initRequest)); } - var call = objectServiceClient!.Put(null, ctx.Deadline, ctx.CancellationToken); + var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); await call.RequestStream.WriteAsync(initRequest); @@ -372,98 +356,16 @@ internal class ObjectServiceProvider : ContextAccessor throw new ArgumentNullException(nameof(initRequest)); } - var call = objectServiceClient!.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); return new SearchReader(call); - } - - private Object.Object CreateObject(ModelsV2.Object @object) - { - var grpcHeader = @object.Header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); - - if (@object.Payload != null) - { - grpcHeader.PayloadLength = (ulong)@object.Payload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); - } - - var split = @object.Header.Split; - if (split != null) - { - grpcHeader.Split = new Header.Types.Split - { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null - }; - - if (split.Children != null && split.Children.Count != 0) - grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); - - if (split.ParentHeader is not null) - { - var grpcParentHeader = CreateHeader(split.ParentHeader, []); - - grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; - grpcHeader.Split.ParentHeader = grpcParentHeader; - grpcHeader.Split.ParentSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), - }; - - split.Parent = grpcHeader.Split.Parent.ToModel(); - } - - grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); - } - - var obj = new Object.Object - { - Header = grpcHeader, - ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, - Payload = ByteString.CopyFrom(@object.Payload) - }; - - obj.Signature = new Refs.Signature - { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), - }; - - return obj; - } - - private Header CreateHeader(ObjectHeader header, byte[]? payload) - { - var grpcHeader = header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); - - if (header.PayloadCheckSum != null) - grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); - else if (payload != null) - grpcHeader.PayloadHash = Sha256Checksum(payload); - - return grpcHeader; - } - - private static Checksum Sha256Checksum(byte[] data) - { - return new Checksum - { - Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(data.Sha256()) - }; - } + } private async Task GetOrCreateSession(Context ctx) { if (string.IsNullOrEmpty(ctx.SessionToken)) { - return await Context.SessionService!.CreateSessionAsync(uint.MaxValue, ctx); + return await Context.Client.CreateSessionInternalAsync(uint.MaxValue, ctx); } return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs new file mode 100644 index 0000000..95ba626 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs @@ -0,0 +1,104 @@ +using System.Linq; + +using Google.Protobuf; + +using FrostFS.Object; +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2; + +internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) +{ + internal ObjectId CalculateObjectId(ObjectHeader header) + { + var grpcHeader = CreateHeader(header, []); + + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + } + + internal Object.Object CreateObject(ModelsV2.Object @object) + { + var grpcHeader = @object.Header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (@object.Payload != null) + { + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + } + + var split = @object.Header.Split; + if (split != null) + { + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Count != 0) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, []); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + + grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); + } + + var obj = new Object.Object + { + Header = grpcHeader, + ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + Payload = ByteString.CopyFrom(@object.Payload) + }; + + obj.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), + }; + + return obj; + } + + internal Header CreateHeader(ObjectHeader header, byte[]? payload) + { + var grpcHeader = header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (header.PayloadCheckSum != null) + grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); + else if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + + return grpcHeader; + } + + internal static Checksum Sha256Checksum(byte[] data) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(data.Sha256()) + }; + } + +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 037078b..b37e7fc 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,3 +1,4 @@ +using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; using Grpc.Net.Client; using System; @@ -5,7 +6,7 @@ using System.Security.Cryptography; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable { internal OwnerId Owner { get; } = owner; internal GrpcChannel Channel { get; private set; } = channel; @@ -13,16 +14,7 @@ public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, Mo internal ModelsV2.Version Version { get; } = version; internal NetworkSettings? NetworkSettings { get; set; } - internal ContainerServiceProvider? ContainerService { get; set; } - internal NetmapServiceProvider? NetmapService { get; set; } - internal SessionServiceProvider? SessionService { get; set; } - internal ObjectServiceProvider? ObjectService { get; set; } - - internal bool Initialized => - ContainerService != null - && NetmapService != null - && SessionService != null - && ObjectService != null; + internal Client Client { get; set; } = client; public void Dispose() { diff --git a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs new file mode 100644 index 0000000..e6424b6 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK.ModelsV2; + +public class CallStatistics +{ + public string MethodName { get; set; } + public long ElapsedMicroSeconds { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs index 302dafc..e486464 100644 --- a/src/FrostFS.SDK.ModelsV2/Context.cs +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -1,13 +1,26 @@ using System; +using System.Collections.Generic; using System.Threading; +using FrostFS.SDK.ModelsV2; +using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; public class Context() { + private List interceptors; + public CancellationToken CancellationToken { get; set; } = default; public TimeSpan Timeout { get; set; } = default; public string SessionToken { get; set; } = string.Empty; public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; + + public Action Callback { get; set; } + + public List Interceptors + { + get { return interceptors ??= []; } + set { interceptors = value; } + } } diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index 0f47676..be180b0 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -9,6 +9,10 @@ true + + + + diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj index 0a559ed..0a28822 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/ClientTestLive.cs index f596472..bfd50ca 100644 --- a/src/FrostFS.SDK.Tests/ClientTestLive.cs +++ b/src/FrostFS.SDK.Tests/ClientTestLive.cs @@ -7,6 +7,8 @@ using FrostFS.SDK.ModelsV2.Netmap; using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Options; +using Grpc.Core.Interceptors; +using System.Diagnostics; namespace FrostFS.SDK.Tests; @@ -18,13 +20,7 @@ public class ClientTestLive [Fact] public async void NetworkMapTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); var result = await fsClient.GetNetmapSnapshotAsync(); @@ -36,20 +32,14 @@ public class ClientTestLive Assert.Equal(13, item.Version.Minor); Assert.Equal(NodeState.Online, item.State); Assert.True(item.PublicKey.Length > 0); - Assert.Single(item.Addresses); + Assert.Single(item.Addresses); Assert.Equal(9, item.Attributes.Count); } [Fact] public async void NodeInfoTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); var result = await fsClient.GetNodeInfoAsync(); @@ -64,27 +54,30 @@ public class ClientTestLive [Fact] public async void SimpleScenarioTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); - await Cleanup(fsClient); var containerId = await fsClient.CreateContainerAsync( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))), + new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + } + ); - var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10), + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); Random rnd = new(); - var bytes = new byte[6*1024*1024 + 100]; + var bytes = new byte[6 * 1024 * 1024 + 100]; rnd.NextBytes(bytes); var param = new PutObjectParameters @@ -97,7 +90,10 @@ public class ClientTestLive ClientCut = false }; - var objectId = await fsClient.PutObjectAsync(param); + var objectId = await fsClient.PutObjectAsync(param, new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }); var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); @@ -121,7 +117,7 @@ public class ClientTestLive MemoryStream ms = new(downloadedBytes); byte[]? chunk = null; - while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk); } @@ -141,26 +137,28 @@ public class ClientTestLive [Fact] public async void ClientCutScenarioTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); await Cleanup(fsClient); var containerId = await fsClient.CreateContainerAsync( new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); - var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10) + }; + + var metrics = new MetricsInterceptor(); + + context.Interceptors.Add(metrics); + var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); Random rnd = new(); - var bytes = new byte[6*1024*1024 + 100]; + var bytes = new byte[6 * 1024 * 1024 + 100]; rnd.NextBytes(bytes); var param = new PutObjectParameters @@ -193,11 +191,11 @@ public class ClientTestLive var @object = await fsClient.GetObjectAsync(containerId, objectId!); - var downloadedBytes = new byte[@object.Header.PayloadLength]; + var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); byte[]? chunk = null; - while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk); } @@ -237,7 +235,7 @@ public class ClientTestLive { try { - await Task.Delay(100); + await Task.Delay(100); return await fsClient.GetContainerAsync(id, ctx); } catch (ApplicationException) @@ -252,3 +250,35 @@ public class ClientTestLive } } } + +public class MetricsInterceptor() : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + return response; + } +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs deleted file mode 100644 index bbf1476..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Security.Cryptography; -using FrostFS.Container; -using Moq; - -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; - -namespace FrostFS.SDK.Tests; - -public abstract class ServiceBase(string key) -{ - public ECDsa Key { get; private set; } = key.LoadWif(); - public ModelsV2.Version Version { get; set; } = DefaultVersion; - public BasicAcl Acl { get; set; } = DefaultAcl; - public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; - - public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); - public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; - public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); -} - -public abstract class ContainerServiceBase(string key) : ServiceBase (key) -{ - public Guid ContainerGuid { get; set; } = Guid.NewGuid(); - - public abstract Mock GetMock(); -} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs deleted file mode 100644 index a752d10..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs +++ /dev/null @@ -1,69 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; - -namespace FrostFS.SDK.Tests; - -public class DeleteContainerMock(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var v = mock.Setup(x => x.DeleteAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())); - - - v.Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - var deleteResponse = new Object.DeleteResponse - { - Body = new Object.DeleteResponse.Types.Body - { - Tombstone = new Refs.Address - { - ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - } - }, - MetaHeader = new ResponseMetaHeader() - }; - - var metadata = new Metadata(); - - return new AsyncUnaryCall( - Task.FromResult(deleteResponse), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - }); - - return mock; - } -} - - - // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => - // { - // return new - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // } - // }; - // }); - - - \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs deleted file mode 100644 index 61feb5e..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs +++ /dev/null @@ -1,163 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; - -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC; - -namespace FrostFS.SDK.Tests; - -public class GetContainerMock(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var grpcVersion = Version.ToGrpcMessage(); - - var getResponse = new GetResponse - { - Body = new GetResponse.Types.Body - { - Container = new Container.Container - { - Version = grpcVersion, - Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), - BasicAcl = (uint)Acl, - PlacementPolicy = PlacementPolicy.ToGrpcMessage() - } - }, - MetaHeader = new ResponseMetaHeader - { - Version = grpcVersion, - Epoch = 100, - Ttl = 1 - } - }; - - getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); - - var metadata = new Metadata(); - var getContainerResponse = new AsyncUnaryCall( - Task.FromResult(getResponse), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - - mock.Setup(x => x.GetAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Verifier.CheckRequest(r); - - return getContainerResponse; - }); - - return mock; - } - - private ResponseVerificationHeader GetResponseVerificationHeader(GetResponse response) - { - var verifyHeader = new ResponseVerificationHeader - { - MetaSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) - }, - BodySignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) - }, - OriginSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData([])) - } - }; - - return verifyHeader; - } -} - -// objectServiceClientMock.Setup( -// x => x.Put(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns((Metadata m, DateTime dt, CancellationToken ct) => -// { -// return new AsyncClientStreamingCall(null, null, null, null, null, null); - -// //IClientStreamWriter requestStream, Task responseAsync, Task responseHeadersAsync, Func getStatusFunc, Func getTrailersFunc, Action disposeAction -// }); - -// return objectServiceClientMock; -// } - - -// } - - - // public virtual global::FrostFS.Object.HeadResponse Head(global::FrostFS.Object.HeadRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - // { - // return Head(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - // } - - // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => - // { - // return new - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // } - // }; - // }); - - - // objectServiceClientMock.Setup(x => x.Delete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - // { - // return new Object.DeleteResponse - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // }, - // MetaHeader = new ResponseMetaHeader() - // { - // }, - // VerifyHeader = new ResponseVerificationHeader() - // { - // MetaSignature = new Refs.Signature - // { - // Key = ByteString.CopyFrom(_key.PublicKey()), - // Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - // Sign = ByteString.CopyFrom(_key.SignData(Array.Empty())) - - // // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), - // } - // } - - // }; - // }); \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ClientTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs similarity index 57% rename from src/FrostFS.SDK.Tests/ClientTest.cs rename to src/FrostFS.SDK.Tests/ContainerTest.cs index e543b7f..b915304 100644 --- a/src/FrostFS.SDK.Tests/ClientTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -3,18 +3,20 @@ using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + using Microsoft.Extensions.Options; namespace FrostFS.SDK.Tests; -public class ClientTest +public class ContainerTest { private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; [Fact] public async void CreateContainerTest() { - var factory = new PutContainerMock(this.key) + var factory = new PutContainerMockFactory(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), @@ -30,10 +32,10 @@ public class ClientTest var fsClient = Client.GetTestInstance( settings, null, - new NetmapMock(this.key).GetMock().Object, - new SessionMock(this.key).GetMock().Object, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, factory.GetMock().Object, - new ObjectMock(this.key).GetMock().Object); + new ObjectMockFactory(this.key).GetMock().Object); var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); @@ -45,7 +47,7 @@ public class ClientTest [Fact] public async void GetContainerTest() { - var factory = new GetContainerMock(this.key) + var factory = new GetContainerMockFactory(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), @@ -62,10 +64,10 @@ public class ClientTest var fsClient = Client.GetTestInstance( settings, null, - new NetmapMock(this.key).GetMock().Object, - new SessionMock(this.key).GetMock().Object, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, factory.GetMock().Object, - new ObjectMock(this.key).GetMock().Object); + new ObjectMockFactory(this.key).GetMock().Object); var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); @@ -80,8 +82,38 @@ public class ClientTest Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); } - // [Fact] - // public async void DeleteObjectAsyncTest() - // { - // } + [Fact] + public async void DeleteContainerAsyncTest() + { + var factory = new DeleteContainerMockFactory(this.key) + { + Version = new ModelsV2.Version(2, 13), + Acl = BasicAcl.PublicRW, + ContainerGuid = Guid.NewGuid(), + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMockFactory(this.key).GetMock().Object); + + var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); + + await fsClient.DeleteContainerAsync(cid); + + Assert.Single(factory.Requests); + + var request = factory.Requests.First(); + + Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); + } } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs new file mode 100644 index 0000000..0ea346d --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -0,0 +1,78 @@ +using System.Security.Cryptography; +using FrostFS.Container; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.Session; +using Google.Protobuf; + +using FrostFS.SDK.ClientV2; +using FrostFS.Object; +using Grpc.Core; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public abstract class ServiceBase(string key) +{ + public ECDsa Key { get; private set; } = key.LoadWif(); + public ModelsV2.Version Version { get; set; } = DefaultVersion; + public BasicAcl Acl { get; set; } = DefaultAcl; + public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; + + public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); + public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; + public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); + + public Metadata ResponseMetaData => []; + + protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } + + public ResponseMetaHeader ResponseMetaHeader => new() + { + Version = Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + }; +} + +public abstract class ContainerServiceBase(string key) : ServiceBase (key) +{ + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); + + public abstract Mock GetMock(); +} + +public abstract class ObjectServiceBase(string key) : ServiceBase (key) +{ + public abstract Mock GetMock(); + + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs new file mode 100644 index 0000000..ba03978 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs @@ -0,0 +1,54 @@ +using FrostFS.Container; +using FrostFS.Session; +using Grpc.Core; +using Moq; + +namespace FrostFS.SDK.Tests; + +public class DeleteContainerMockFactory(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Requests.Add(new RequestData(r, m, dt, ct)); + + var response = new DeleteResponse + { + Body = new DeleteResponse.Types.Body(), + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + }); + + return mock; + } + + public List> Requests = []; +} + +public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) +{ + public T Request => request; + public Metadata Metadata => m; + public DateTime? deadline => dt; + public CancellationToken CancellationToken => ct; +} + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs new file mode 100644 index 0000000..3a17e88 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs @@ -0,0 +1,13 @@ +using FrostFS.Container; +using Moq; + + +namespace FrostFS.SDK.Tests; + +public class ContainerStub(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + return new Mock(); + } +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs new file mode 100644 index 0000000..8b2a496 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -0,0 +1,58 @@ +using FrostFS.Container; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public class GetContainerMockFactory(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var grpcVersion = Version.ToGrpcMessage(); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Container = new Container.Container + { + Version = grpcVersion, + Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), + BasicAcl = (uint)Acl, + PlacementPolicy = PlacementPolicy.ToGrpcMessage() + } + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.GetAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs similarity index 97% rename from src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs rename to src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs index 3243bdf..c6abeab 100644 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs @@ -11,7 +11,7 @@ using FrostFS.Refs; namespace FrostFS.SDK.Tests; -public class PutContainerMock(string key) : ContainerServiceBase(key) +public class PutContainerMockFactory(string key) : ContainerServiceBase(key) { public override Mock GetMock() { diff --git a/src/FrostFS.SDK.Tests/NetmapMock.cs b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs similarity index 78% rename from src/FrostFS.SDK.Tests/NetmapMock.cs rename to src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs index 0cc3889..07264ff 100644 --- a/src/FrostFS.SDK.Tests/NetmapMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs @@ -3,7 +3,7 @@ using FrostFS.Netmap; namespace FrostFS.SDK.Tests; -public class NetmapMock(string key) : ServiceBase(key) +public class NetmapMockFactory(string key) : ServiceBase(key) { public Mock GetMock() { diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs new file mode 100644 index 0000000..1d6c9e1 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -0,0 +1,101 @@ +using Moq; +using FrostFS.Object; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using FrostFS.Session; +using Google.Protobuf; +using System.Security.Cryptography; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.Tests; + +public class ObjectMockFactory(string key) : ObjectServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + GetResponse response = new() + { + Body = new GetResponse.Types.Body + { + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.Get( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncServerStreamingCall( + new AsyncStreamReaderMock(key, ObjectHeader), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } + + public ObjectHeader ObjectHeader { get; set; } +} + +public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader +{ + public GetResponse Current + { + get + { + var ecdsaKey = key.LoadWif(); + + var header = new Header + { + ContainerId = objectHeader.ContainerId.ToGrpcMessage(), + PayloadLength = objectHeader.PayloadLength, + Version = objectHeader.Version!.ToGrpcMessage(), + OwnerId = objectHeader.OwnerId!.ToGrpcMessage() + }; + + foreach (var attr in objectHeader.Attributes) + header.Attributes.Add(attr.ToGrpcMessage()); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Init = new GetResponse.Types.Body.Types.Init + { + Header = header, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, + Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(ecdsaKey.PublicKey()), + Sign = ByteString.CopyFrom(ecdsaKey.SignData(header.ToByteArray())), + } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return response; + } + } + + public Task MoveNext(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs new file mode 100644 index 0000000..83beaf8 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -0,0 +1,57 @@ +using Moq; +using FrostFS.Session; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using Google.Protobuf; + +namespace FrostFS.SDK.Tests; + +public class SessionMockFactory(string key) : ServiceBase(key) +{ + public byte[]? SessionId { get; set; } + + public byte[]? SessionKey { get; set; } + + public Mock GetMock() + { + var mock = new Mock(); + + Random rand = new(); + SessionId = new byte[32]; + SessionKey = new byte[32]; + + rand.NextBytes(SessionId); + rand.NextBytes(SessionKey); + + CreateResponse response = new() + { + Body = new CreateResponse.Types.Body + { + Id = ByteString.CopyFrom(SessionId), + SessionKey = ByteString.CopyFrom(SessionId) + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.CreateAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ObjectMock.cs b/src/FrostFS.SDK.Tests/ObjectMock.cs deleted file mode 100644 index 96296f8..0000000 --- a/src/FrostFS.SDK.Tests/ObjectMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Object; - -namespace FrostFS.SDK.Tests; - -public class ObjectMock(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -} diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs new file mode 100644 index 0000000..53c2a9a --- /dev/null +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -0,0 +1,66 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; + +using System.Security.Cryptography; + +namespace FrostFS.SDK.Tests; + +public class ObjectTest +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + [Fact] + public async void GetObjectTest() + { + var ecdsaKey = key.LoadWif(); + ContainerId cntId = new("xyz"); + + ObjectHeader header = new(cntId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) + { + PayloadLength = 1, + Version = new ModelsV2.Version(2, 13), + OwnerId = OwnerId.FromKey(ecdsaKey) + }; + + var objectMockFactory = new ObjectMockFactory(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid(), + ObjectHeader = header + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, + new ContainerStub(this.key).GetMock().Object, + objectMockFactory.GetMock().Object); + + var objectId = fsClient.CalculateObjectId(header); + + var containerId = new ContainerId(Base58.Encode(objectMockFactory.ContainerGuid.ToBytes())); + + var result = await fsClient.GetObjectAsync(containerId, objectId); + + Assert.NotNull(result); + + Assert.Equal(header.ContainerId.Value, result.Header.ContainerId.Value); + Assert.Equal(header.OwnerId.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); + Assert.Equal(header.PayloadLength, result.Header.PayloadLength); + Assert.Single(result.Header.Attributes); + Assert.Equal(header.Attributes[0].Key, result.Header.Attributes[0].Key); + Assert.Equal(header.Attributes[0].Value,result.Header.Attributes[0].Value); + } +} diff --git a/src/FrostFS.SDK.Tests/SessionMock.cs b/src/FrostFS.SDK.Tests/SessionMock.cs deleted file mode 100644 index 8309fde..0000000 --- a/src/FrostFS.SDK.Tests/SessionMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Session; - -namespace FrostFS.SDK.Tests; - -public class SessionMock(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -}