[#14] Add interceptors
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:
Pavel Gross 2024-07-01 11:56:47 +03:00 committed by p.gross
parent 605463ec24
commit ae67b12313
28 changed files with 943 additions and 554 deletions

View file

@ -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;
@ -21,9 +23,14 @@ 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()
});
}

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

View file

@ -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);
#endregion
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
#region Tools
ObjectId CalculateObjectId(ObjectHeader header);
GrpcChannel Channel { get; }
#endregion
}

View file

@ -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)

View file

@ -81,10 +81,6 @@ internal class ContainerServiceProvider : ContextAccessor
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);
return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray()));

View file

@ -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,43 +89,36 @@ 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());
}
internal ObjectId CalculateObjectId(ObjectHeader header)
{
var grpcHeader = CreateHeader(header, []);
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
}
internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx)
@ -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();
var @object = obj.ToModel();
modelObject.ObjectReader = reader;
@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();

View 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())
};
}
}

View file

@ -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()
{

View file

@ -0,0 +1,7 @@
namespace FrostFS.SDK.ModelsV2;
public class CallStatistics
{
public string MethodName { get; set; }
public long ElapsedMicroSeconds { get; set; }
}

View file

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

View file

@ -10,6 +10,10 @@
<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" />
</ItemGroup>

View file

@ -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>

View file

@ -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();
@ -43,13 +39,7 @@ public class ClientTestLive
[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,20 +54,23 @@ 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), 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))));
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);
@ -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,20 +137,22 @@ 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);
@ -197,7 +195,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);
}
@ -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;
}
}

View file

@ -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();
}

View file

@ -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]) }
// }
// }
// };
// });

View file

@ -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())),
// }
// }
// };
// });

View file

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

View file

@ -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();
}

View file

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

View file

@ -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>();
}
}

View file

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

View file

@ -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()
{

View file

@ -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()
{

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

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

View file

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

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

View file

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