[#15] Add interceptors #15
28 changed files with 943 additions and 554 deletions
|
@ -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<ClientSettings> clientOptions, GrpcChannelOptions channelOptions)
|
||||
public static IFrostFSClient GetInstance(IOptions<ClientSettings> 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.
|
||||
/// </summary>
|
||||
/// <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="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
||||
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
||||
|
@ -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<ClientSettings> options, GrpcChannelOptions channelOptions)
|
||||
private Client(IOptions<ClientSettings> 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<ModelsV2.Container> 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<ContainerId> ListContainersAsync(Context? ctx = default)
|
||||
{
|
||||
ValidateEnvironment(ref ctx);
|
||||
return ClientCtx.ContainerService!.ListContainersAsync(ctx!);
|
||||
var service = GetContainerService(ref ctx);
|
||||
return service.ListContainersAsync(ctx!);
|
||||
}
|
||||
|
||||
public Task<ContainerId> 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<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
|
||||
{
|
||||
ValidateEnvironment(ref ctx);
|
||||
return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!);
|
||||
var service = GetNetmapService(ref ctx);
|
||||
return service.GetNetmapSnapshotAsync(ctx!);
|
||||
}
|
||||
|
||||
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
|
||||
{
|
||||
ValidateEnvironment(ref ctx);
|
||||
return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!);
|
||||
var service = GetNetmapService(ref 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)
|
||||
{
|
||||
ValidateEnvironment(ref ctx);
|
||||
return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!);
|
||||
var service = GetObjectService(ref ctx);
|
||||
return service.GetObjectHeadAsync(containerId, objectId, ctx!);
|
||||
}
|
||||
|
||||
public Task<ModelsV2.Object> 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<ObjectId> 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<ObjectId> 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<ObjectId> SearchObjectsAsync(
|
||||
|
@ -184,19 +197,38 @@ public class Client : IFrostFSClient
|
|||
IEnumerable<ObjectFilter> 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<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)
|
||||
{
|
||||
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()
|
||||
});
|
||||
}
|
||||
|
|
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.Netmap;
|
||||
using Grpc.Net.Client;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Interfaces;
|
||||
|
||||
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);
|
||||
|
||||
IAsyncEnumerable<ContainerId> ListContainersAsync(Context? context = default);
|
||||
|
@ -17,7 +29,9 @@ public interface IFrostFSClient : IDisposable
|
|||
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? context = default);
|
||||
|
||||
Task DeleteContainerAsync(ContainerId containerId, Context? context = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
Task<ObjectHeader> GetObjectHeadAsync(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);
|
||||
|
||||
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, IEnumerable<ObjectFilter> filters, Context? context = default);
|
||||
|
||||
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
|
||||
|
||||
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
ObjectId CalculateObjectId(ObjectHeader header);
|
||||
|
||||
GrpcChannel Channel { get; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<ObjectHeader> 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<ObjectId> PutSingleObjectAsync(ModelsV2.Object @object, Context ctx)
|
||||
internal async Task<ObjectId> 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<ObjectId> 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<Session.SessionToken> 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();
|
||||
|
|
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 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()
|
||||
{
|
||||
|
|
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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
|
||||
public class Context()
|
||||
{
|
||||
private List<Interceptor> 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<CallStatistics> Callback { get; set; }
|
||||
|
||||
public List<Interceptor> Interceptors
|
||||
{
|
||||
get { return interceptors ??= []; }
|
||||
set { interceptors = value; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
<PropertyGroup>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.63.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -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<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.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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
public class PutContainerMock(string key) : ContainerServiceBase(key)
|
||||
public class PutContainerMockFactory(string key) : ContainerServiceBase(key)
|
||||
{
|
||||
public override Mock<ContainerService.ContainerServiceClient> GetMock()
|
||||
{
|
|
@ -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<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