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;
- }
-}