[#14] Add interceptors
All checks were successful
DCO / DCO (pull_request) Successful in 43s
All checks were successful
DCO / DCO (pull_request) Successful in 43s
Signed-off-by: Pavel Gross <p.gross@yadro.com>
This commit is contained in:
parent
605463ec24
commit
ae67b12313
28 changed files with 943 additions and 554 deletions
|
@ -7,11 +7,13 @@ using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Version = FrostFS.SDK.ModelsV2.Version;
|
using Version = FrostFS.SDK.ModelsV2.Version;
|
||||||
|
|
||||||
|
@ -21,9 +23,14 @@ public class Client : IFrostFSClient
|
||||||
{
|
{
|
||||||
private bool isDisposed;
|
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; }
|
internal ClientEnvironment ClientCtx { get; set; }
|
||||||
|
|
||||||
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions channelOptions)
|
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
||||||
{
|
{
|
||||||
return new Client(clientOptions, channelOptions);
|
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.
|
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientOptions">Global setting for client</param>
|
/// <param name="clientOptions">Global setting for client</param>
|
||||||
/// <param name="channelOptions">Setting for gRPC cjannel</param>
|
/// <param name="channelOptions">Setting for gRPC channel</param>
|
||||||
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
|
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
|
||||||
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
||||||
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
||||||
|
@ -61,18 +68,19 @@ public class Client : IFrostFSClient
|
||||||
OwnerId.FromKey(ecdsaKey);
|
OwnerId.FromKey(ecdsaKey);
|
||||||
|
|
||||||
ClientCtx = new ClientEnvironment(
|
ClientCtx = new ClientEnvironment(
|
||||||
|
this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: OwnerId.FromKey(ecdsaKey),
|
owner: OwnerId.FromKey(ecdsaKey),
|
||||||
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
|
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
|
||||||
version: new Version(2, 13));
|
version: new Version(2, 13));
|
||||||
|
|
||||||
ClientCtx.ContainerService = new ContainerServiceProvider(containerService, ClientCtx);
|
ContainerServiceClient = containerService;
|
||||||
ClientCtx.NetmapService = new NetmapServiceProvider(netmapService, ClientCtx);
|
NetmapServiceClient = netmapService;
|
||||||
ClientCtx.SessionService = new SessionServiceProvider(sessionService, ClientCtx);
|
SessionServiceClient = sessionService;
|
||||||
ClientCtx.ObjectService = new ObjectServiceProvider(objectService, ClientCtx);
|
ObjectServiceClient = objectService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Client(IOptions<ClientSettings> options, GrpcChannelOptions channelOptions)
|
private Client(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
|
||||||
{
|
{
|
||||||
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
|
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);
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||||
|
|
||||||
ClientCtx = new ClientEnvironment(
|
ClientCtx = new ClientEnvironment(
|
||||||
|
this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: OwnerId.FromKey(ecdsaKey),
|
owner: OwnerId.FromKey(ecdsaKey),
|
||||||
channel: channel,
|
channel: channel,
|
||||||
version: new Version(2, 13));
|
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();
|
CheckFrostFsVersionSupport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,72 +115,81 @@ public class Client : IFrostFSClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GrpcChannel Channel => ClientCtx.Channel;
|
#region ContainerImplementation
|
||||||
|
|
||||||
public Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? ctx = null)
|
public Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? ctx = null)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetContainerService(ref ctx);
|
||||||
return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!);
|
return service.GetContainerAsync(containerId, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ContainerId> ListContainersAsync(Context? ctx = default)
|
public IAsyncEnumerable<ContainerId> ListContainersAsync(Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetContainerService(ref ctx);
|
||||||
return ClientCtx.ContainerService!.ListContainersAsync(ctx!);
|
return service.ListContainersAsync(ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? ctx = null)
|
public Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? ctx = null)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetContainerService(ref ctx);
|
||||||
return ClientCtx.ContainerService!.CreateContainerAsync(container, ctx!);
|
return service.CreateContainerAsync(container, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default)
|
public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetContainerService(ref ctx);
|
||||||
return ClientCtx.ContainerService!.DeleteContainerAsync(containerId, ctx!);
|
return service.DeleteContainerAsync(containerId, ctx!);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NetworkImplementation
|
||||||
public Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
|
public Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetNetmapService(ref ctx);
|
||||||
return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!);
|
return service.GetNetmapSnapshotAsync(ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
|
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetNetmapService(ref ctx);
|
||||||
return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!);
|
return service.GetLocalNodeInfoAsync(ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<NetworkSettings> GetNetworkSettingsAsync(Context? ctx = default)
|
||||||
|
{
|
||||||
|
var service = GetNetmapService(ref ctx);
|
||||||
|
return service.GetNetworkSettingsAsync(ctx!);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ObjectImplementation
|
||||||
public Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!);
|
return service.GetObjectHeadAsync(containerId, objectId, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!);
|
return service.GetObjectAsync(containerId, objectId, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default)
|
public Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!);
|
return service.PutObjectAsync(putObjectParameters, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default)
|
public Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.PutSingleObjectAsync(obj, ctx!);
|
return service.PutSingleObjectAsync(obj, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.DeleteObjectAsync(containerId, objectId, ctx!);
|
return service.DeleteObjectAsync(containerId, objectId, ctx!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ObjectId> SearchObjectsAsync(
|
public IAsyncEnumerable<ObjectId> SearchObjectsAsync(
|
||||||
|
@ -184,19 +197,38 @@ public class Client : IFrostFSClient
|
||||||
IEnumerable<ObjectFilter> filters,
|
IEnumerable<ObjectFilter> filters,
|
||||||
Context? ctx = default)
|
Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetObjectService(ref ctx);
|
||||||
return ClientCtx.ObjectService!.SearchObjectsAsync(containerId, filters, ctx!);
|
return service.SearchObjectsAsync(containerId, filters, ctx!);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SessionImplementation
|
||||||
|
public async Task<ModelsV2.SessionToken> CreateSessionAsync(ulong expiration, Context? ctx = null)
|
||||||
|
{
|
||||||
|
var session = await CreateSessionInternalAsync(expiration, ctx);
|
||||||
|
var token = session.Serialize();
|
||||||
|
|
||||||
|
return new ModelsV2.SessionToken([], token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<Session.SessionToken> CreateSessionInternalAsync(ulong expiration, Context? ctx = null)
|
||||||
|
{
|
||||||
|
var service = GetSessionService(ref ctx);
|
||||||
|
return service.CreateSessionAsync(expiration, ctx!);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ToolsImplementation
|
||||||
public ObjectId CalculateObjectId(ObjectHeader header)
|
public ObjectId CalculateObjectId(ObjectHeader header)
|
||||||
{
|
{
|
||||||
return ClientCtx.ObjectService!.CalculateObjectId(header);
|
return new ObjectTools(ClientCtx).CalculateObjectId(header);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
private async void CheckFrostFsVersionSupport(Context? ctx = default)
|
private async void CheckFrostFsVersionSupport(Context? ctx = default)
|
||||||
{
|
{
|
||||||
ValidateEnvironment(ref ctx);
|
var service = GetNetmapService(ref ctx);
|
||||||
var localNodeInfo = await ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!);
|
var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!);
|
||||||
|
|
||||||
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
|
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)
|
if (isDisposed)
|
||||||
throw new Exception("Client is disposed.");
|
throw new Exception("Client is disposed.");
|
||||||
|
|
||||||
if (ClientCtx == null || !ClientCtx.Initialized)
|
|
||||||
throw new Exception("Client is not initialized.");
|
|
||||||
|
|
||||||
ctx ??= new Context();
|
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)
|
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
|
||||||
|
@ -230,21 +323,14 @@ public class Client : IFrostFSClient
|
||||||
throw new ArgumentException(msg);
|
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)
|
if (channelOptions != null)
|
||||||
{
|
{
|
||||||
return GrpcChannel.ForAddress(uri, channelOptions);
|
return GrpcChannel.ForAddress(uri, channelOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return GrpcChannel.ForAddress(uri, new GrpcChannelOptions
|
return GrpcChannel.ForAddress(uri, new GrpcChannelOptions
|
||||||
{
|
{
|
||||||
Credentials = grpcCredentials,
|
|
||||||
HttpHandler = new HttpClientHandler()
|
HttpHandler = new HttpClientHandler()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
73
src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs
Normal file
73
src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs
Normal file
|
@ -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<CallStatistics> callback) : Interceptor
|
||||||
|
{
|
||||||
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(request, context);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<TResponse>(
|
||||||
|
HandleUnaryResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(context);
|
||||||
|
|
||||||
|
return new AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
call.RequestStream,
|
||||||
|
HandleStreamResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> 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<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,24 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
using Grpc.Net.Client;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2.Interfaces;
|
namespace FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
|
||||||
public interface IFrostFSClient : IDisposable
|
public interface IFrostFSClient : IDisposable
|
||||||
{
|
{
|
||||||
|
#region Network
|
||||||
|
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
|
||||||
|
|
||||||
|
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
|
||||||
|
|
||||||
|
Task<NetworkSettings> GetNetworkSettingsAsync(Context? context = default);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Session
|
||||||
|
Task<SessionToken> CreateSessionAsync(ulong expiration, Context? context = default);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Container
|
||||||
Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? context = default);
|
Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? context = default);
|
||||||
|
|
||||||
IAsyncEnumerable<ContainerId> ListContainersAsync(Context? context = default);
|
IAsyncEnumerable<ContainerId> ListContainersAsync(Context? context = default);
|
||||||
|
@ -17,7 +29,9 @@ public interface IFrostFSClient : IDisposable
|
||||||
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? context = default);
|
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? context = default);
|
||||||
|
|
||||||
Task DeleteContainerAsync(ContainerId containerId, Context? context = default);
|
Task DeleteContainerAsync(ContainerId containerId, Context? context = default);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Object
|
||||||
Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
||||||
|
|
||||||
Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
Task<ModelsV2.Object> 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);
|
Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
||||||
|
|
||||||
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, IEnumerable<ObjectFilter> filters, Context? context = default);
|
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, IEnumerable<ObjectFilter> filters, Context? context = default);
|
||||||
|
#endregion
|
||||||
|
|
||||||
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
|
#region Tools
|
||||||
|
|
||||||
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
|
|
||||||
|
|
||||||
ObjectId CalculateObjectId(ObjectHeader header);
|
ObjectId CalculateObjectId(ObjectHeader header);
|
||||||
|
#endregion
|
||||||
GrpcChannel Channel { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
using System;
|
using System;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public static class SessionMapper
|
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()];
|
byte[] bytes = new byte[token.CalculateSize()];
|
||||||
CodedOutputStream stream = new(bytes);
|
CodedOutputStream stream = new(bytes);
|
||||||
token.WriteTo(stream);
|
token.WriteTo(stream);
|
||||||
|
|
||||||
return Convert.ToBase64String(bytes);
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes)
|
internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes)
|
||||||
|
|
|
@ -81,10 +81,6 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
|
|
||||||
var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
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);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray()));
|
return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray()));
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using FrostFS.Object;
|
using FrostFS.Object;
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
@ -17,15 +15,9 @@ using FrostFS.SDK.ClientV2.Extensions;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal class ObjectServiceProvider : ContextAccessor
|
internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx)
|
||||||
{
|
{
|
||||||
private readonly ObjectService.ObjectServiceClient objectServiceClient;
|
readonly ObjectTools tools = new(ctx);
|
||||||
|
|
||||||
internal ObjectServiceProvider(ObjectService.ObjectServiceClient objectServiceClient, ClientEnvironment context)
|
|
||||||
: base (context)
|
|
||||||
{
|
|
||||||
this.objectServiceClient = objectServiceClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task<ObjectHeader> GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx)
|
internal async Task<ObjectHeader> GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx)
|
||||||
{
|
{
|
||||||
|
@ -44,7 +36,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader();
|
||||||
request.Sign(Context.Key);
|
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);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -97,43 +89,36 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
return PutStreamObject(parameters, ctx);
|
return PutStreamObject(parameters, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object @object, Context ctx)
|
internal async Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object modelObject, Context ctx)
|
||||||
{
|
{
|
||||||
var sessionToken = await GetOrCreateSession(ctx);
|
var sessionToken = await GetOrCreateSession(ctx);
|
||||||
|
|
||||||
var obj = CreateObject(@object);
|
var grpcObject = tools.CreateObject(modelObject);
|
||||||
|
|
||||||
var request = new PutSingleRequest
|
var request = new PutSingleRequest
|
||||||
{
|
{
|
||||||
Body = new PutSingleRequest.Types.Body()
|
Body = new PutSingleRequest.Types.Body()
|
||||||
{
|
{
|
||||||
Object = obj
|
Object = grpcObject
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader();
|
||||||
request.AddObjectSessionToken(
|
request.AddObjectSessionToken(
|
||||||
sessionToken,
|
sessionToken,
|
||||||
obj.Header.ContainerId,
|
grpcObject.Header.ContainerId,
|
||||||
obj.ObjectId,
|
grpcObject.ObjectId,
|
||||||
ObjectSessionContext.Types.Verb.Put,
|
ObjectSessionContext.Types.Verb.Put,
|
||||||
Context.Key
|
Context.Key
|
||||||
);
|
);
|
||||||
|
|
||||||
request.Sign(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);
|
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)
|
internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx)
|
||||||
|
@ -153,7 +138,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader();
|
||||||
request.Sign(Context.Key);
|
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);
|
Verifier.CheckResponse(response);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +180,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
List<ObjectId> sentObjectIds = [];
|
List<ObjectId> sentObjectIds = [];
|
||||||
ModelsV2.Object? currentObject;
|
ModelsV2.Object? currentObject;
|
||||||
|
|
||||||
var networkSettings = await Context.NetmapService!.GetNetworkSettingsAsync(ctx);
|
var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx);
|
||||||
|
|
||||||
var partSize = (int)networkSettings.MaxObjectSize;
|
var partSize = (int)networkSettings.MaxObjectSize;
|
||||||
var buffer = new byte[partSize];
|
var buffer = new byte[partSize];
|
||||||
|
@ -215,7 +200,6 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
largeObject.AppendBlock(buffer, bytesCount);
|
largeObject.AppendBlock(buffer, bytesCount);
|
||||||
|
|
||||||
currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer)
|
currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer)
|
||||||
.AddAttributes(header.Attributes)
|
|
||||||
.SetSplit(split);
|
.SetSplit(split);
|
||||||
|
|
||||||
if (largeObject.PayloadLength == fullLength)
|
if (largeObject.PayloadLength == fullLength)
|
||||||
|
@ -228,6 +212,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
|
|
||||||
if (sentObjectIds.Count != 0)
|
if (sentObjectIds.Count != 0)
|
||||||
{
|
{
|
||||||
|
largeObject.AddAttributes(parameters.Header!.Attributes);
|
||||||
largeObject.CalculateHash();
|
largeObject.CalculateHash();
|
||||||
|
|
||||||
currentObject.SetParent(largeObject);
|
currentObject.SetParent(largeObject);
|
||||||
|
@ -238,11 +223,14 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject)
|
var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject)
|
||||||
.AddChildren(sentObjectIds);
|
.AddChildren(sentObjectIds);
|
||||||
|
|
||||||
|
linkObject.Header.Attributes.Clear();
|
||||||
|
|
||||||
_ = await PutSingleObjectAsync(linkObject, ctx);
|
_ = await PutSingleObjectAsync(linkObject, ctx);
|
||||||
|
|
||||||
return CalculateObjectId(largeObject.Header);
|
return tools.CalculateObjectId(largeObject.Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentObject.AddAttributes(parameters.Header!.Attributes);
|
||||||
return await PutSingleObjectAsync(currentObject, ctx);
|
return await PutSingleObjectAsync(currentObject, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,10 +245,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
hdr.OwnerId = Context.Owner.ToGrpcMessage();
|
hdr.OwnerId = Context.Owner.ToGrpcMessage();
|
||||||
hdr.Version = Context.Version.ToGrpcMessage();
|
hdr.Version = Context.Version.ToGrpcMessage();
|
||||||
|
|
||||||
var oid = new ObjectID
|
var oid = new ObjectID { Value = hdr.Sha256() };
|
||||||
{
|
|
||||||
Value = hdr.Sha256()
|
|
||||||
};
|
|
||||||
|
|
||||||
var request = new PutRequest
|
var request = new PutRequest
|
||||||
{
|
{
|
||||||
|
@ -269,7 +254,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
Init = new PutRequest.Types.Body.Types.Init
|
Init = new PutRequest.Types.Body.Types.Init
|
||||||
{
|
{
|
||||||
Header = hdr
|
Header = hdr
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -314,13 +299,12 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
var reader = GetObjectInit(request, ctx);
|
var reader = GetObjectInit(request, ctx);
|
||||||
|
|
||||||
var obj = await reader.ReadHeader();
|
var grpcObject = await reader.ReadHeader();
|
||||||
|
var modelObject = grpcObject.ToModel();
|
||||||
|
|
||||||
var @object = obj.ToModel();
|
modelObject.ObjectReader = reader;
|
||||||
|
|
||||||
@object.ObjectReader = reader;
|
return modelObject;
|
||||||
|
|
||||||
return @object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx)
|
private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx)
|
||||||
|
@ -328,7 +312,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
if (initRequest is null)
|
if (initRequest is null)
|
||||||
throw new ArgumentNullException(nameof(initRequest));
|
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);
|
return new ObjectReader(call);
|
||||||
}
|
}
|
||||||
|
@ -340,7 +324,7 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
throw new ArgumentNullException(nameof(initRequest));
|
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);
|
await call.RequestStream.WriteAsync(initRequest);
|
||||||
|
|
||||||
|
@ -372,98 +356,16 @@ internal class ObjectServiceProvider : ContextAccessor
|
||||||
throw new ArgumentNullException(nameof(initRequest));
|
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);
|
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<Session.SessionToken> GetOrCreateSession(Context ctx)
|
private async Task<Session.SessionToken> GetOrCreateSession(Context ctx)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ctx.SessionToken))
|
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();
|
return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken();
|
||||||
|
|
104
src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs
Normal file
104
src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs
Normal file
|
@ -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())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using System;
|
using System;
|
||||||
|
@ -5,7 +6,7 @@ using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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 OwnerId Owner { get; } = owner;
|
||||||
internal GrpcChannel Channel { get; private set; } = channel;
|
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 ModelsV2.Version Version { get; } = version;
|
||||||
internal NetworkSettings? NetworkSettings { get; set; }
|
internal NetworkSettings? NetworkSettings { get; set; }
|
||||||
|
|
||||||
internal ContainerServiceProvider? ContainerService { get; set; }
|
internal Client Client { get; set; } = client;
|
||||||
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;
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
7
src/FrostFS.SDK.ModelsV2/CallStatistics.cs
Normal file
7
src/FrostFS.SDK.ModelsV2/CallStatistics.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
public class CallStatistics
|
||||||
|
{
|
||||||
|
public string MethodName { get; set; }
|
||||||
|
public long ElapsedMicroSeconds { get; set; }
|
||||||
|
}
|
|
@ -1,13 +1,26 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class Context()
|
public class Context()
|
||||||
{
|
{
|
||||||
|
private List<Interceptor> interceptors;
|
||||||
|
|
||||||
public CancellationToken CancellationToken { get; set; } = default;
|
public CancellationToken CancellationToken { get; set; } = default;
|
||||||
public TimeSpan Timeout { get; set; } = default;
|
public TimeSpan Timeout { get; set; } = default;
|
||||||
public string SessionToken { get; set; } = string.Empty;
|
public string SessionToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
||||||
|
|
||||||
|
public Action<CallStatistics> Callback { get; set; }
|
||||||
|
|
||||||
|
public List<Interceptor> Interceptors
|
||||||
|
{
|
||||||
|
get { return interceptors ??= []; }
|
||||||
|
set { interceptors = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.63.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
|
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.62.0" />
|
<PackageReference Include="Grpc.Net.Client" Version="2.63.0" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.64.0">
|
<PackageReference Include="Grpc.Tools" Version="2.64.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|
|
@ -7,6 +7,8 @@ using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
@ -18,13 +20,7 @@ public class ClientTestLive
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NetworkMapTest()
|
public async void NetworkMapTest()
|
||||||
{
|
{
|
||||||
var channelOptions = new GrpcChannelOptions
|
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
{
|
|
||||||
Credentials = ChannelCredentials.Insecure,
|
|
||||||
HttpHandler = new HttpClientHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
|
|
||||||
|
|
||||||
var result = await fsClient.GetNetmapSnapshotAsync();
|
var result = await fsClient.GetNetmapSnapshotAsync();
|
||||||
|
|
||||||
|
@ -43,13 +39,7 @@ public class ClientTestLive
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NodeInfoTest()
|
public async void NodeInfoTest()
|
||||||
{
|
{
|
||||||
var channelOptions = new GrpcChannelOptions
|
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
{
|
|
||||||
Credentials = ChannelCredentials.Insecure,
|
|
||||||
HttpHandler = new HttpClientHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
|
|
||||||
|
|
||||||
var result = await fsClient.GetNodeInfoAsync();
|
var result = await fsClient.GetNodeInfoAsync();
|
||||||
|
|
||||||
|
@ -64,20 +54,23 @@ public class ClientTestLive
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void SimpleScenarioTest()
|
public async void SimpleScenarioTest()
|
||||||
{
|
{
|
||||||
var channelOptions = new GrpcChannelOptions
|
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
{
|
|
||||||
Credentials = ChannelCredentials.Insecure,
|
|
||||||
HttpHandler = new HttpClientHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
|
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
await Cleanup(fsClient);
|
||||||
|
|
||||||
var containerId = await fsClient.CreateContainerAsync(
|
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);
|
var container = await GetContainer(fsClient, containerId, context);
|
||||||
|
|
||||||
|
@ -97,7 +90,10 @@ public class ClientTestLive
|
||||||
ClientCut = false
|
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");
|
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
@ -121,7 +117,7 @@ public class ClientTestLive
|
||||||
MemoryStream ms = new(downloadedBytes);
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
byte[]? chunk = null;
|
byte[]? chunk = null;
|
||||||
while ((chunk = await @object.ObjectReader.ReadChunk()) != null)
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
{
|
{
|
||||||
ms.Write(chunk);
|
ms.Write(chunk);
|
||||||
}
|
}
|
||||||
|
@ -141,20 +137,22 @@ public class ClientTestLive
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ClientCutScenarioTest()
|
public async void ClientCutScenarioTest()
|
||||||
{
|
{
|
||||||
var channelOptions = new GrpcChannelOptions
|
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
{
|
|
||||||
Credentials = ChannelCredentials.Insecure,
|
|
||||||
HttpHandler = new HttpClientHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
|
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
await Cleanup(fsClient);
|
||||||
|
|
||||||
var containerId = await fsClient.CreateContainerAsync(
|
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))));
|
||||||
|
|
||||||
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);
|
var container = await GetContainer(fsClient, containerId, context);
|
||||||
|
|
||||||
Assert.NotNull(container);
|
Assert.NotNull(container);
|
||||||
|
@ -197,7 +195,7 @@ public class ClientTestLive
|
||||||
MemoryStream ms = new(downloadedBytes);
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
byte[]? chunk = null;
|
byte[]? chunk = null;
|
||||||
while ((chunk = await @object.ObjectReader.ReadChunk()) != null)
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
{
|
{
|
||||||
ms.Write(chunk);
|
ms.Write(chunk);
|
||||||
}
|
}
|
||||||
|
@ -252,3 +250,35 @@ public class ClientTestLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MetricsInterceptor() : Interceptor
|
||||||
|
{
|
||||||
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(request, context);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<TResponse>(
|
||||||
|
HandleUnaryResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock();
|
|
||||||
}
|
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock()
|
|
||||||
{
|
|
||||||
var mock = new Mock<ContainerService.ContainerServiceClient>();
|
|
||||||
|
|
||||||
var v = mock.Setup(x => x.DeleteAsync(
|
|
||||||
It.IsAny<DeleteRequest>(),
|
|
||||||
It.IsAny<Metadata>(),
|
|
||||||
It.IsAny<DateTime?>(),
|
|
||||||
It.IsAny<CancellationToken>()));
|
|
||||||
|
|
||||||
|
|
||||||
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<Object.DeleteResponse>(
|
|
||||||
Task.FromResult(deleteResponse),
|
|
||||||
Task.FromResult(metadata),
|
|
||||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
|
||||||
() => metadata,
|
|
||||||
() => { });
|
|
||||||
});
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// objectServiceClientMock.Setup(x => x.Head(It.IsAny<Object.HeadRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
|
||||||
// .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]) }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock()
|
|
||||||
{
|
|
||||||
var mock = new Mock<ContainerService.ContainerServiceClient>();
|
|
||||||
|
|
||||||
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<GetResponse>(
|
|
||||||
Task.FromResult(getResponse),
|
|
||||||
Task.FromResult(metadata),
|
|
||||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
|
||||||
() => metadata,
|
|
||||||
() => { });
|
|
||||||
|
|
||||||
mock.Setup(x => x.GetAsync(
|
|
||||||
It.IsAny<GetRequest>(),
|
|
||||||
It.IsAny<Metadata>(),
|
|
||||||
It.IsAny<DateTime?>(),
|
|
||||||
It.IsAny<CancellationToken>()))
|
|
||||||
.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<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
|
||||||
// .Returns((Metadata m, DateTime dt, CancellationToken ct) =>
|
|
||||||
// {
|
|
||||||
// return new AsyncClientStreamingCall<FrostFS.Object.PutRequest, FrostFS.Object.PutResponse>(null, null, null, null, null, null);
|
|
||||||
|
|
||||||
// //IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> 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<Object.HeadRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
|
||||||
// .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<Object.DeleteRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime?>(), It.IsAny<CancellationToken>()))
|
|
||||||
// .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<byte>()))
|
|
||||||
|
|
||||||
// // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// };
|
|
||||||
// });
|
|
|
@ -3,18 +3,20 @@ using FrostFS.SDK.Cryptography;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
using FrostFS.SDK.ModelsV2.Enums;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public class ClientTest
|
public class ContainerTest
|
||||||
{
|
{
|
||||||
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void CreateContainerTest()
|
public async void CreateContainerTest()
|
||||||
{
|
{
|
||||||
var factory = new PutContainerMock(this.key)
|
var factory = new PutContainerMockFactory(this.key)
|
||||||
{
|
{
|
||||||
PlacementPolicy = new PlacementPolicy(true, new Replica(1)),
|
PlacementPolicy = new PlacementPolicy(true, new Replica(1)),
|
||||||
Version = new ModelsV2.Version(2, 13),
|
Version = new ModelsV2.Version(2, 13),
|
||||||
|
@ -30,10 +32,10 @@ public class ClientTest
|
||||||
var fsClient = Client.GetTestInstance(
|
var fsClient = Client.GetTestInstance(
|
||||||
settings,
|
settings,
|
||||||
null,
|
null,
|
||||||
new NetmapMock(this.key).GetMock().Object,
|
new NetmapMockFactory(this.key).GetMock().Object,
|
||||||
new SessionMock(this.key).GetMock().Object,
|
new SessionMockFactory(this.key).GetMock().Object,
|
||||||
factory.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));
|
var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy));
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ public class ClientTest
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void GetContainerTest()
|
public async void GetContainerTest()
|
||||||
{
|
{
|
||||||
var factory = new GetContainerMock(this.key)
|
var factory = new GetContainerMockFactory(this.key)
|
||||||
{
|
{
|
||||||
PlacementPolicy = new PlacementPolicy(true, new Replica(1)),
|
PlacementPolicy = new PlacementPolicy(true, new Replica(1)),
|
||||||
Version = new ModelsV2.Version(2, 13),
|
Version = new ModelsV2.Version(2, 13),
|
||||||
|
@ -62,10 +64,10 @@ public class ClientTest
|
||||||
var fsClient = Client.GetTestInstance(
|
var fsClient = Client.GetTestInstance(
|
||||||
settings,
|
settings,
|
||||||
null,
|
null,
|
||||||
new NetmapMock(this.key).GetMock().Object,
|
new NetmapMockFactory(this.key).GetMock().Object,
|
||||||
new SessionMock(this.key).GetMock().Object,
|
new SessionMockFactory(this.key).GetMock().Object,
|
||||||
factory.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()));
|
var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes()));
|
||||||
|
|
||||||
|
@ -80,8 +82,38 @@ public class ClientTest
|
||||||
Assert.Equal(factory.Version.ToString(), result.Version!.ToString());
|
Assert.Equal(factory.Version.ToString(), result.Version!.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Fact]
|
[Fact]
|
||||||
// public async void DeleteObjectAsyncTest()
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ObjectServiceBase(string key) : ServiceBase (key)
|
||||||
|
{
|
||||||
|
public abstract Mock<ObjectService.ObjectServiceClient> GetMock();
|
||||||
|
|
||||||
|
public Guid ContainerGuid { get; set; } = Guid.NewGuid();
|
||||||
|
}
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock()
|
||||||
|
{
|
||||||
|
var mock = new Mock<ContainerService.ContainerServiceClient>();
|
||||||
|
|
||||||
|
var v = mock.Setup(x => x.DeleteAsync(
|
||||||
|
It.IsAny<DeleteRequest>(),
|
||||||
|
It.IsAny<Metadata>(),
|
||||||
|
It.IsAny<DateTime?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
Requests.Add(new RequestData<DeleteRequest>(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<DeleteResponse>(
|
||||||
|
Task.FromResult(response),
|
||||||
|
Task.FromResult(metadata),
|
||||||
|
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||||
|
() => metadata,
|
||||||
|
() => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RequestData<DeleteRequest>> Requests = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RequestData<T>(T request, Metadata m, DateTime? dt, CancellationToken ct)
|
||||||
|
{
|
||||||
|
public T Request => request;
|
||||||
|
public Metadata Metadata => m;
|
||||||
|
public DateTime? deadline => dt;
|
||||||
|
public CancellationToken CancellationToken => ct;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using FrostFS.Container;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
public class ContainerStub(string key) : ContainerServiceBase(key)
|
||||||
|
{
|
||||||
|
public override Mock<ContainerService.ContainerServiceClient> GetMock()
|
||||||
|
{
|
||||||
|
return new Mock<ContainerService.ContainerServiceClient>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ContainerService.ContainerServiceClient> GetMock()
|
||||||
|
{
|
||||||
|
var mock = new Mock<ContainerService.ContainerServiceClient>();
|
||||||
|
|
||||||
|
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<GetRequest>(),
|
||||||
|
It.IsAny<Metadata>(),
|
||||||
|
It.IsAny<DateTime?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
Verifier.CheckRequest(r);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<GetResponse>(
|
||||||
|
Task.FromResult(response),
|
||||||
|
Task.FromResult(ResponseMetaData),
|
||||||
|
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||||
|
() => ResponseMetaData,
|
||||||
|
() => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ using FrostFS.Refs;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public class PutContainerMock(string key) : ContainerServiceBase(key)
|
public class PutContainerMockFactory(string key) : ContainerServiceBase(key)
|
||||||
{
|
{
|
||||||
public override Mock<ContainerService.ContainerServiceClient> GetMock()
|
public override Mock<ContainerService.ContainerServiceClient> GetMock()
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using FrostFS.Netmap;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public class NetmapMock(string key) : ServiceBase(key)
|
public class NetmapMockFactory(string key) : ServiceBase(key)
|
||||||
{
|
{
|
||||||
public Mock<NetmapService.NetmapServiceClient> GetMock()
|
public Mock<NetmapService.NetmapServiceClient> GetMock()
|
||||||
{
|
{
|
101
src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs
Normal file
101
src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs
Normal file
|
@ -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<ObjectService.ObjectServiceClient> GetMock()
|
||||||
|
{
|
||||||
|
var mock = new Mock<ObjectService.ObjectServiceClient>();
|
||||||
|
|
||||||
|
GetResponse response = new()
|
||||||
|
{
|
||||||
|
Body = new GetResponse.Types.Body
|
||||||
|
{
|
||||||
|
},
|
||||||
|
MetaHeader = ResponseMetaHeader
|
||||||
|
};
|
||||||
|
|
||||||
|
response.VerifyHeader = GetResponseVerificationHeader(response);
|
||||||
|
|
||||||
|
mock.Setup(x => x.Get(
|
||||||
|
It.IsAny<GetRequest>(),
|
||||||
|
It.IsAny<Metadata>(),
|
||||||
|
It.IsAny<DateTime?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
Verifier.CheckRequest(r);
|
||||||
|
|
||||||
|
return new AsyncServerStreamingCall<GetResponse>(
|
||||||
|
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<GetResponse>
|
||||||
|
{
|
||||||
|
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<byte>())) },
|
||||||
|
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<bool> MoveNext(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
57
src/FrostFS.SDK.Tests/Mocks/SessionMock.cs
Normal file
57
src/FrostFS.SDK.Tests/Mocks/SessionMock.cs
Normal file
|
@ -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<SessionService.SessionServiceClient> GetMock()
|
||||||
|
{
|
||||||
|
var mock = new Mock<SessionService.SessionServiceClient>();
|
||||||
|
|
||||||
|
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<CreateRequest>(),
|
||||||
|
It.IsAny<Metadata>(),
|
||||||
|
It.IsAny<DateTime?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
Verifier.CheckRequest(r);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<CreateResponse>(
|
||||||
|
Task.FromResult(response),
|
||||||
|
Task.FromResult(ResponseMetaData),
|
||||||
|
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||||
|
() => ResponseMetaData,
|
||||||
|
() => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
using Moq;
|
|
||||||
using FrostFS.Object;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
|
||||||
|
|
||||||
public class ObjectMock(string key) : ServiceBase(key)
|
|
||||||
{
|
|
||||||
public Mock<ObjectService.ObjectServiceClient> GetMock()
|
|
||||||
{
|
|
||||||
var mock = new Mock<ObjectService.ObjectServiceClient>();
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
}
|
|
||||||
}
|
|
66
src/FrostFS.SDK.Tests/ObjectTest.cs
Normal file
66
src/FrostFS.SDK.Tests/ObjectTest.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
using Moq;
|
|
||||||
using FrostFS.Session;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
|
||||||
|
|
||||||
public class SessionMock(string key) : ServiceBase(key)
|
|
||||||
{
|
|
||||||
public Mock<SessionService.SessionServiceClient> GetMock()
|
|
||||||
{
|
|
||||||
var mock = new Mock<SessionService.SessionServiceClient>();
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue