[#24] Client: Implement pool part1 #31

Merged
PavelGrossSpb merged 3 commits from PavelGrossSpb/frostfs-sdk-csharp:poll into master 2024-11-20 15:44:04 +00:00
63 changed files with 801 additions and 526 deletions
Showing only changes of commit ee20798379 - Show all commits

View file

@ -0,0 +1,18 @@
using System;
namespace FrostFS.SDK.ClientV2;
public class FrostFsInvalidObjectException : FrostFsException
{
public FrostFsInvalidObjectException()
{
}
public FrostFsInvalidObjectException(string message) : base(message)
{
}
public FrostFsInvalidObjectException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -0,0 +1,25 @@
using System;
namespace FrostFS.SDK.ClientV2;
public class FrostFsResponseException : FrostFsException
{
public FrostFsResponseStatus? Status { get; private set; }
public FrostFsResponseException()
{
}
public FrostFsResponseException(FrostFsResponseStatus status)
{
Status = status;
}
public FrostFsResponseException(string message) : base(message)
{
}
public FrostFsResponseException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -0,0 +1,18 @@
using System;
namespace FrostFS.SDK.ClientV2;
public class FrostFsStreamException : FrostFsException
{
public FrostFsStreamException()
{
}
public FrostFsStreamException(string message) : base(message)
{
}
public FrostFsStreamException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -1,18 +0,0 @@
using System;
namespace FrostFS.SDK.ClientV2;
public class InvalidObjectException : Exception
{
public InvalidObjectException()
{
}
public InvalidObjectException(string message) : base(message)
{
}
public InvalidObjectException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -1,25 +0,0 @@
using System;
namespace FrostFS.SDK.ClientV2;
public class ResponseException : Exception
{
public FrostFsResponseStatus? Status { get; private set; }
public ResponseException()
{
}
public ResponseException(FrostFsResponseStatus status)
{
Status = status;
}
public ResponseException(string message) : base(message)
{
}
public ResponseException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -22,10 +22,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -39,7 +39,7 @@ public class FrostFSClient : IFrostFSClient
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
internal EnvironmentContext ClientCtx { get; set; }
internal ClientContext ClientCtx { get; set; }
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
{
@ -93,7 +93,7 @@ public class FrostFSClient : IFrostFSClient
var ecdsaKey = settings.Value.Key.LoadWif();
FrostFsOwner.FromKey(ecdsaKey);
ClientCtx = new EnvironmentContext(
ClientCtx = new ClientContext(
client: this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@ -108,13 +108,13 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new EnvironmentContext(
ClientCtx = new ClientContext(
this,
key: null,
owner: null,
@ -127,7 +127,7 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
@ -135,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new EnvironmentContext(
ClientCtx = new ClientContext(
this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@ -146,14 +146,17 @@ public class FrostFSClient : IFrostFSClient
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
}
internal FrostFSClient(WrapperPrm prm)
internal FrostFSClient(WrapperPrm prm, SessionCache cache)
{
ClientCtx = new EnvironmentContext(
ClientCtx = new ClientContext(
client: this,
key: prm.Key,
owner: FrostFsOwner.FromKey(prm.Key!),
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
version: new FrostFsVersion(2, 13));
version: new FrostFsVersion(2, 13))
{
SessionCache = cache
};
}
public void Dispose()
@ -363,10 +366,10 @@ public class FrostFSClient : IFrostFSClient
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
{
var args = new PrmNodeInfo { Context = ctx };
var args = new PrmNodeInfo(ctx);
if (ctx?.Version == null)
throw new InvalidObjectException(nameof(ctx.Version));
throw new ArgumentNullException(nameof(ctx), "Version must be initialized");
var service = GetNetmapService(args);
var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false);
@ -378,18 +381,16 @@ public class FrostFSClient : IFrostFSClient
}
}
private CallInvoker? SetupEnvironment(IContext ctx)
private CallInvoker? SetupClientContext(IContext ctx)
{
if (isDisposed)
throw new InvalidObjectException("Client is disposed.");
throw new FrostFsInvalidObjectException("Client is disposed.");
ctx.Context ??= new CallContext();
if (ctx.Context.Key == null)
if (ctx.Context!.Key == null)
{
if (ClientCtx.Key == null)
{
throw new InvalidObjectException("Key is not initialized.");
throw new ArgumentNullException(nameof(ctx), "Key is not initialized.");
}
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
@ -404,24 +405,23 @@ public class FrostFSClient : IFrostFSClient
{
if (ClientCtx.Version == null)
{
throw new InvalidObjectException("Version is not initialized.");
throw new ArgumentNullException(nameof(ctx), "Version is not initialized.");
}
ctx.Context.Version = ClientCtx.Version;
}
CallInvoker? callInvoker = null;
if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0)
{
foreach (var interceptor in ctx.Context.Interceptors)
{
callInvoker = AddInvoker(callInvoker, interceptor);
}
}
foreach (var interceptor in ctx.Context.Interceptors)
callInvoker = AddInvoker(callInvoker, interceptor);
if (ctx.Context.Callback != null)
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
if (ctx.Context.PoolErrorHandler != null)
callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler));
return callInvoker;
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
@ -429,7 +429,7 @@ public class FrostFSClient : IFrostFSClient
if (callInvoker == null)
callInvoker = ClientCtx.Channel.Intercept(interceptor);
else
callInvoker.Intercept(interceptor);
callInvoker = callInvoker.Intercept(interceptor);
return callInvoker;
}
@ -437,7 +437,7 @@ public class FrostFSClient : IFrostFSClient
private NetmapServiceProvider GetNetmapService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = NetmapServiceClient ?? (callInvoker != null
? new NetmapService.NetmapServiceClient(callInvoker)
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
@ -447,7 +447,7 @@ public class FrostFSClient : IFrostFSClient
private SessionServiceProvider GetSessionService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = SessionServiceClient ?? (callInvoker != null
? new SessionService.SessionServiceClient(callInvoker)
: new SessionService.SessionServiceClient(ClientCtx.Channel));
@ -457,7 +457,7 @@ public class FrostFSClient : IFrostFSClient
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = ApeManagerServiceClient ?? (callInvoker != null
? new APEManagerService.APEManagerServiceClient(callInvoker)
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
@ -467,7 +467,7 @@ public class FrostFSClient : IFrostFSClient
private AccountingServiceProvider GetAccouningService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = AccountingServiceClient ?? (callInvoker != null
? new AccountingService.AccountingServiceClient(callInvoker)
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
@ -477,7 +477,7 @@ public class FrostFSClient : IFrostFSClient
private ContainerServiceProvider GetContainerService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = ContainerServiceClient ?? (callInvoker != null
? new ContainerService.ContainerServiceClient(callInvoker)
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
@ -487,7 +487,7 @@ public class FrostFSClient : IFrostFSClient
private ObjectServiceProvider GetObjectService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = ObjectServiceClient ?? (callInvoker != null
? new ObjectService.ObjectServiceClient(callInvoker)
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
@ -497,7 +497,7 @@ public class FrostFSClient : IFrostFSClient
private AccountingServiceProvider GetAccountService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
var callInvoker = SetupClientContext(ctx);
var client = AccountingServiceClient ?? (callInvoker != null
? new AccountingService.AccountingServiceClient(callInvoker)
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
@ -527,19 +527,12 @@ public class FrostFSClient : IFrostFSClient
public async Task<string?> Dial(CallContext ctx)
{
try
{
var prm = new PrmBalance { Context = ctx };
var prm = new PrmBalance(ctx);
var service = GetAccouningService(prm);
var balance = await service.GetBallance(prm).ConfigureAwait(false);
var service = GetAccouningService(prm);
_ = await service.GetBallance(prm).ConfigureAwait(false);
return null;
}
catch (FrostFsException ex)
{
return ex.Message;
}
return null;
}
public bool RestartIfUnhealthy(CallContext ctx)

View file

@ -0,0 +1,68 @@
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace FrostFS.SDK.ClientV2;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
Justification = "parameters are provided by GRPC infrastructure")]
public class ErrorInterceptor(Action<Exception> handler) : 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)
{
try
{
return await call;
}
catch (Exception ex)
{
handler(ex);
throw;
}
}
private async Task<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> call)
{
try
{
return await call;
}
catch (Exception ex)
{
handler(ex);
throw;
}
}
}

View file

@ -7,6 +7,8 @@ using Grpc.Core.Interceptors;
namespace FrostFS.SDK.ClientV2;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
Justification = "parameters are provided by GRPC infrastructure")]
public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
@ -14,11 +16,6 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
if (continuation is null)
{
throw new ArgumentNullException(nameof(continuation));
}
var call = continuation(request, context);
return new AsyncUnaryCall<TResponse>(
@ -33,9 +30,6 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
ClientInterceptorContext<TRequest, TResponse> context,
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
{
if (continuation is null)
throw new ArgumentNullException(nameof(continuation));
var call = continuation(context);
return new AsyncClientStreamingCall<TRequest, TResponse>(
@ -52,7 +46,7 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
var watch = new Stopwatch();
watch.Start();
var response = await call.ResponseAsync.ConfigureAwait(false);
var response = await call;
watch.Stop();
@ -68,7 +62,7 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
var watch = new Stopwatch();
watch.Start();
var response = await call.ResponseAsync.ConfigureAwait(false);
var response = await call;
watch.Stop();

View file

@ -61,7 +61,5 @@ public interface IFrostFSClient : IDisposable
public Task<string?> Dial(CallContext ctx);
public bool RestartIfUnhealthy(CallContext ctx);
public void Close();
}

View file

@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
namespace FrostFS.SDK.ClientV2;
internal static partial class FrostFsMessages
{
[LoggerMessage(100,
LogLevel.Warning,
"Failed to create frostfs session token for client. Address {address}, {error}",
EventName = nameof(SessionCreationError))]
internal static partial void SessionCreationError(ILogger logger, string address, string error);
[LoggerMessage(101,
LogLevel.Warning,
"Error threshold reached. Address {address}, threshold {threshold}",
EventName = nameof(ErrorЕhresholdReached))]
internal static partial void ErrorЕhresholdReached(ILogger logger, string address, uint threshold);
[LoggerMessage(102,
LogLevel.Warning,
"Health has changed: {address} healthy {healthy}, reason {error}",
EventName = nameof(HealthChanged))]
internal static partial void HealthChanged(ILogger logger, string address, bool healthy, string error);
}

View file

@ -15,7 +15,7 @@ public class ClientSettings
{
var errors = CheckFields();
if (errors != null)
ThrowException(errors);
ThrowSettingsException(errors);
}
protected Collection<string>? CheckFields()
@ -29,7 +29,7 @@ public class ClientSettings
return null;
}
protected static void ThrowException(Collection<string> errors)
protected static void ThrowSettingsException(Collection<string> errors)
{
if (errors is null)
{
@ -55,7 +55,7 @@ public class SingleOwnerClientSettings : ClientSettings
{
var errors = CheckFields();
if (errors != null)
ThrowException(errors);
ThrowSettingsException(errors);
}
protected new Collection<string>? CheckFields()

View file

@ -31,7 +31,7 @@ public class FrostFsContainerId
return this.modelId;
}
throw new InvalidObjectException();
throw new FrostFsInvalidObjectException();
}
internal ContainerID ContainerID
@ -47,7 +47,7 @@ public class FrostFsContainerId
return this.containerID;
}
throw new InvalidObjectException();
throw new FrostFsInvalidObjectException();
}
}

View file

@ -88,7 +88,7 @@ public class FrostFsContainerInfo
{
if (PlacementPolicy == null)
{
throw new InvalidObjectException("PlacementPolicy is null");
throw new ArgumentNullException("PlacementPolicy is null");
}
this.container = new Container.Container()

View file

@ -1,4 +1,4 @@
using FrostFS.SDK.ClientV2;
using System;
namespace FrostFS.SDK;
@ -67,7 +67,7 @@ public class FrostFsObject
public void SetParent(FrostFsObjectHeader largeObjectHeader)
{
if (Header?.Split == null)
throw new InvalidObjectException("The object is not initialized properly");
throw new ArgumentNullException(nameof(largeObjectHeader), "Split value must not be null");
Header.Split.ParentHeader = largeObjectHeader;
}

View file

@ -13,10 +13,10 @@ namespace FrostFS.SDK.ClientV2;
public class CallContext()
{
private ReadOnlyCollection<Interceptor>? interceptors;
private ByteString? publicKeyCache;
internal Action<Exception>? PoolErrorHandler { get; set; }
public ECDsa? Key { get; set; }
public FrostFsOwner? OwnerId { get; set; }
@ -31,11 +31,7 @@ public class CallContext()
public Action<CallStatistics>? Callback { get; set; }
public ReadOnlyCollection<Interceptor>? Interceptors
{
get { return this.interceptors; }
set { this.interceptors = value; }
}
public Collection<Interceptor> Interceptors { get; } = [];
public ByteString? GetPublicKeyCache()
{

View file

@ -7,5 +7,5 @@ public interface IContext
/// callbacks, interceptors.
/// </summary>
/// <value>Additional parameters for calling the method</value>
CallContext? Context { get; set; }
CallContext? Context { get; }
}

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmApeChainList(FrostFsChainTarget target) : PrmBase
public sealed class PrmApeChainList(FrostFsChainTarget target, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;
}

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain) : PrmBase
public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain) : PrmBase
public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;

View file

@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmBalance() : PrmBase
public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx)
{
}

View file

@ -2,7 +2,7 @@
namespace FrostFS.SDK.ClientV2;
public class PrmBase(NameValueCollection? xheaders = null) : IContext
public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext
{
/// <summary>
/// FrostFS request X-Headers
@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext
public NameValueCollection XHeaders { get; } = xheaders ?? [];
/// <inheritdoc />
public CallContext? Context { get; set; }
public CallContext Context { get; } = ctx ?? new CallContext();
}

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmContainerCreate(FrostFsContainerInfo container) : PrmBase, ISessionToken
public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerInfo Container { get; set; } = container;

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmContainerDelete(FrostFsContainerId containerId) : PrmBase, ISessionToken
public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmContainerGet(FrostFsContainerId container) : PrmBase
public sealed class PrmContainerGet(FrostFsContainerId container, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsContainerId Container { get; set; } = container;
}

View file

@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmContainerGetAll() : PrmBase()
public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx)
{
}

View file

@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmNetmapSnapshot() : PrmBase
public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx)
{
}

View file

@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmNetworkSettings() : PrmBase
public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx)
{
}

View file

@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmNodeInfo() : PrmBase
public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx)
{
}

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;

View file

@ -2,7 +2,7 @@ using System.IO;
namespace FrostFS.SDK.ClientV2;
public sealed class PrmObjectPut : PrmBase, ISessionToken
public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
/// <summary>
/// Need to provide values like <c>ContainerId</c> and <c>ObjectType</c> to create and object.

View file

@ -2,7 +2,7 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmObjectSearch(FrostFsContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken
public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken
{
/// <summary>
/// Defines container for the search

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmSessionCreate(ulong expiration) : PrmBase
public sealed class PrmSessionCreate(ulong expiration, CallContext? ctx = null) : PrmBase(ctx)
{
public ulong Expiration { get; set; } = expiration;
}

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken
public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsObject FrostFsObject { get; set; } = frostFsObject;

View file

@ -27,8 +27,7 @@ public class ClientStatusMonitor : IClientStatus
MethodIndex.methodSessionCreate,
MethodIndex.methodAPEManagerAddChain,
MethodIndex.methodAPEManagerRemoveChain,
MethodIndex.methodAPEManagerListChains,
MethodIndex.methodLast
MethodIndex.methodAPEManagerListChains
];
public static string GetMethodName(MethodIndex index)
@ -53,7 +52,7 @@ public class ClientStatusMonitor : IClientStatus
MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
MethodIndex.methodAPEManagerListChains => "APEManagerListChains",
_ => throw new NotImplementedException(),
_ => throw new ArgumentException("Unknown method", nameof(index)),
};
}
@ -62,13 +61,12 @@ public class ClientStatusMonitor : IClientStatus
private readonly ILogger? logger;
private int healthy;
public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold)
public ClientStatusMonitor(ILogger? logger, string address)
{
this.logger = logger;
healthy = (int)HealthyStatus.Healthy;
Address = address;
ErrorThreshold = errorThreshold;
Address = address;
Methods = new MethodStatus[MethodIndexes.Length];
for (int i = 0; i < MethodIndexes.Length; i++)
@ -79,7 +77,7 @@ public class ClientStatusMonitor : IClientStatus
public string Address { get; }
internal uint ErrorThreshold { get; }
internal uint ErrorThreshold { get; set; }
public uint CurrentErrorCount { get; set; }
@ -89,7 +87,8 @@ public class ClientStatusMonitor : IClientStatus
public bool IsHealthy()
{
return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
return res;
}
public bool IsDialed()
@ -124,14 +123,13 @@ public class ClientStatusMonitor : IClientStatus
if (thresholdReached)
{
SetUnhealthy();
CurrentErrorCount = 0;
}
}
if (thresholdReached)
if (thresholdReached && logger != null)
{
logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold);
FrostFsMessages.ErrorЕhresholdReached(logger, Address, ErrorThreshold);
}
}

View file

@ -1,38 +1,36 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Grpc.Core;
namespace FrostFS.SDK.ClientV2;
// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
public class ClientWrapper
public class ClientWrapper : ClientStatusMonitor
{
private readonly object _lock = new();
public ClientWrapper(WrapperPrm wrapperPrm)
private SessionCache sessionCache;
internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address)
{
WrapperPrm = wrapperPrm;
StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold);
ErrorThreshold = wrapperPrm.ErrorThreshold;
try
{
Client = new FrostFSClient(WrapperPrm);
StatusMonitor.SetHealthy();
}
catch (FrostFsException)
{
}
sessionCache = pool.SessionCache;
Client = new FrostFSClient(WrapperPrm, sessionCache);
}
internal FrostFSClient? Client { get; private set; }
internal WrapperPrm WrapperPrm { get; }
internal ClientStatusMonitor StatusMonitor { get; }
internal FrostFSClient? GetClient()
{
lock (_lock)
{
if (StatusMonitor.IsHealthy())
if (IsHealthy())
{
return Client;
}
@ -44,21 +42,29 @@ public class ClientWrapper
// dial establishes a connection to the server from the FrostFS network.
// Returns an error describing failure reason. If failed, the client
// SHOULD NOT be used.
internal async Task<string?> Dial(CallContext ctx)
internal async Task Dial(CallContext ctx)
{
var client = GetClient();
var client = GetClient() ?? throw new FrostFsInvalidObjectException("pool client unhealthy");
if (client == null)
return "pool client unhealthy";
await client.Dial(ctx).ConfigureAwait(false);
}
var result = await client.Dial(ctx).ConfigureAwait(false);
if (!string.IsNullOrEmpty(result))
internal void HandleError(Exception ex)
{
if (ex is FrostFsResponseException responseException && responseException.Status != null)
{
StatusMonitor.SetUnhealthyOnDial();
return result;
switch (responseException.Status.Code)
{
case FrostFsStatusCode.Internal:
case FrostFsStatusCode.WrongMagicNumber:
case FrostFsStatusCode.SignatureVerificationFailure:
case FrostFsStatusCode.NodeUnderMaintenance:
IncErrorRate();
return;
}
}
return null;
IncErrorRate();
}
private async Task ScheduleGracefulClose()
@ -79,31 +85,31 @@ public class ClientWrapper
try
{
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
var prmNodeInfo = new PrmNodeInfo(ctx);
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
return false;
}
catch (FrostFsException)
catch (RpcException)
{
wasHealthy = true;
}
// if connection is dialed before, to avoid routine/connection leak,
// pool has to close it and then initialize once again.
Review

Could be resolved with

var client FrostFSClient
try {
client = new(WrapperPrm);
...
Client = client;
client = null;
}
finally {
client?.Dispose();
}
Could be resolved with ``` var client FrostFSClient try { client = new(WrapperPrm); ... Client = client; client = null; } finally { client?.Dispose(); } ```
Review

and why do I need a reference to disposed object?

and why do I need a reference to disposed object?
Review

I did not understand the question: where is the reference to disposed object?
There are 4 main steps:

  1. Create temp reference to disposable object
  2. Inside try block create an disposable object to this reference
  3. Inside try block transfer ownership of disposable object from temp reference to new owner and set null to temp reference: now Client field holds reference to disposable object, not temp reference
  4. Dispose disposable object if it is not null
I did not understand the question: where is the reference to disposed object? There are 4 main steps: 1. Create temp reference to disposable object 2. Inside `try` block create an disposable object to this reference 3. Inside `try` block transfer ownership of disposable object from temp reference to new owner and set `null` to temp reference: now `Client` field holds reference to disposable object, not temp reference 4. Dispose disposable object if it is not `null`
Review

Got it. But it looks like a strange trick to avoid dispose pattern warning that follows another analizer warning about "always null variable". The suppressing comment is a good reminder about dispose necessity and keeps the code clean and clear.

Got it. But it looks like a strange trick to avoid dispose pattern warning that follows another analizer warning about "always null variable". The suppressing comment is a good reminder about dispose necessity and keeps the code clean and clear.
Review

I don't think it is a trick, but if you ok with it, I'm ok too.

I don't think it is a trick, but if you ok with it, I'm ok too.
if (StatusMonitor.IsDialed())
if (IsDialed())
{
await ScheduleGracefulClose().ConfigureAwait(false);
}
#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
FrostFSClient client = new(WrapperPrm);
FrostFSClient client = new(WrapperPrm, sessionCache);
#pragma warning restore CA2000
//TODO: set additioanl params
var error = await client.Dial(ctx).ConfigureAwait(false);
if (!string.IsNullOrEmpty(error))
{
StatusMonitor.SetUnhealthyOnDial();
SetUnhealthyOnDial();
return wasHealthy;
}
@ -114,22 +120,22 @@ public class ClientWrapper
try
{
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
var prmNodeInfo = new PrmNodeInfo(ctx);
var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
}
catch (FrostFsException)
{
StatusMonitor.SetUnhealthy();
SetUnhealthy();
return wasHealthy;
}
StatusMonitor.SetHealthy();
SetHealthy();
return !wasHealthy;
}
internal void IncRequests(ulong elapsed, MethodIndex method)
{
var methodStat = StatusMonitor.Methods[(int)method];
var methodStat = Methods[(int)method];
methodStat.IncRequests(elapsed);
}

View file

@ -1,16 +0,0 @@
namespace FrostFS.SDK.ClientV2;
public class DialOptions
{
public bool Block { get; set; }
public bool ReturnLastError { get; set; }
public ulong Timeout { get; set; }
public string? Authority { get; set; }
public bool DisableRetry { get; set; }
public bool DisableHealthCheck { get; set; }
}

View file

@ -1,6 +1,8 @@
using System;
using System.Security.Cryptography;
using Grpc.Net.Client;
using Microsoft.Extensions.Logging;
namespace FrostFS.SDK.ClientV2;
@ -24,7 +26,7 @@ public class InitParameters
public NodeParam[]? NodeParams { get; set; }
public DialOptions[]? DialOptions { get; set; }
public GrpcChannelOptions[]? DialOptions { get; set; }
public Func<string, ClientWrapper>? ClientBuilder { get; set; }

View file

@ -21,7 +21,7 @@ internal sealed class InnerPool
if (Clients.Length == 1)
{
var client = Clients[0];
if (client.StatusMonitor.IsHealthy())
if (client.IsHealthy())
{
return client;
}
@ -34,7 +34,7 @@ internal sealed class InnerPool
{
int index = Sampler.Next();
if (Clients[index].StatusMonitor.IsHealthy())
if (Clients[index].IsHealthy())
{
return Clients[index];
}

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -13,6 +12,8 @@ using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
using Grpc.Core;
using Microsoft.Extensions.Logging;
@ -36,7 +37,7 @@ public partial class Pool : IFrostFSClient
private ECDsa Key { get; set; }
private byte[] PublicKey { get; }
private string PublicKey { get; }
private OwnerID? _ownerId;
private FrostFsOwner? _owner;
@ -65,7 +66,7 @@ public partial class Pool : IFrostFSClient
internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
private SessionCache Cache { get; set; }
internal SessionCache SessionCache { get; set; }
private ulong SessionTokenDuration { get; set; }
@ -75,7 +76,7 @@ public partial class Pool : IFrostFSClient
private bool disposedValue;
private ILogger? Logger { get; set; }
private ILogger? logger { get; set; }
private ulong MaxObjectSize { get; set; }
@ -91,20 +92,20 @@ public partial class Pool : IFrostFSClient
if (options.Key == null)
{
throw new FrostFsException($"Missed required parameter {nameof(options.Key)}");
throw new ArgumentException($"Missed required parameter {nameof(options.Key)}");
}
var nodesParams = AdjustNodeParams(options.NodeParams);
var cache = new SessionCache(options.SessionExpirationDuration);
FillDefaultInitParams(options, cache);
FillDefaultInitParams(options, this);
Key = options.Key;
PublicKey = Key.PublicKey();
PublicKey = $"{Key.PublicKey()}";
Cache = cache;
Logger = options.Logger;
SessionCache = cache;
logger = options.Logger;
SessionTokenDuration = options.SessionExpirationDuration;
RebalanceParams = new RebalanceParameters(
@ -148,47 +149,54 @@ public partial class Pool : IFrostFSClient
for (int j = 0; j < nodeParams.Addresses.Count; j++)
{
var client = ClientBuilder(nodeParams.Addresses[j]);
clients[j] = client;
var error = await client.Dial(ctx).ConfigureAwait(false);
if (!string.IsNullOrEmpty(error))
{
Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error);
continue;
}
ClientWrapper? client = null;
bool dialed = false;
try
{
client = clients[j] = ClientBuilder(nodeParams.Addresses[j]);
await client.Dial(ctx).ConfigureAwait(false);
dialed = true;
var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false)
.ConfigureAwait(false);
var key = FormCacheKey(nodeParams.Addresses[j], Key, false);
_ = Cache.Cache[key] = token;
var key = FormCacheKey(nodeParams.Addresses[j], Key.PrivateKey().ToString());
_ = SessionCache.Cache[key] = token;
atLeastOneHealthy = true;
}
catch (FrostFsException ex)
catch (RpcException ex)
{
client.StatusMonitor.SetUnhealthy();
Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})",
client.WrapperPrm.Address, ex.Message);
if (!dialed)
client!.SetUnhealthyOnDial();
else
client!.SetUnhealthy();
continue;
if (logger != null)
{
FrostFsMessages.SessionCreationError(logger, client!.WrapperPrm.Address, ex.Message);
}
}
catch (FrostFsInvalidObjectException)
{
break;
}
atLeastOneHealthy = true;
}
var sampler = new Sampler(nodeParams.Weights.ToArray());
inner[i] = new InnerPool(sampler, clients);
i++;
}
if (!atLeastOneHealthy)
return "at least one node must be healthy";
return "At least one node must be healthy";
InnerPools = inner;
var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
var res = await GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false);
MaxObjectSize = res.MaxObjectSize;
@ -252,7 +260,7 @@ public partial class Pool : IFrostFSClient
return adjusted;
}
private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache)
private static void FillDefaultInitParams(InitParameters parameters, Pool pool)
{
if (parameters.SessionExpirationDuration == 0)
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
@ -275,8 +283,8 @@ public partial class Pool : IFrostFSClient
if (parameters.NodeStreamTimeout <= 0)
parameters.NodeStreamTimeout = defaultStreamTimeout;
if (cache.TokenDuration == 0)
cache.TokenDuration = defaultSessionTokenExpirationDuration;
if (parameters.SessionExpirationDuration == 0)
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
parameters.ClientBuilder ??= new Func<string, ClientWrapper>((address) =>
{
@ -291,29 +299,29 @@ public partial class Pool : IFrostFSClient
GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
dstepanov-yadro marked this conversation as resolved
Review

See warning

See warning
Review

fixed

fixed
};
return new ClientWrapper(wrapperPrm);
return new ClientWrapper(wrapperPrm, pool);
}
);
}
private FrostFSClient? Сonnection()
private ClientWrapper Сonnection()
{
foreach (var pool in InnerPools!)
{
var client = pool.Connection();
if (client != null)
{
return client.Client;
return client;
}
}
return null;
throw new FrostFsException("Cannot find alive client");
}
private static async Task<FrostFsSessionToken?> InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut)
{
var client = cw.Client;
var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false);
var epoch = networkInfo.Epoch;
@ -321,17 +329,14 @@ public partial class Pool : IFrostFSClient
? ulong.MaxValue
: epoch + duration;
var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx };
var prmSessionCreate = new PrmSessionCreate(exp, ctx);
return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false);
}
private static string FormCacheKey(string address, ECDsa key, bool clientCut)
internal static string FormCacheKey(string address, string key)
{
var k = key.PrivateKey;
var stype = clientCut ? "client" : "server";
return $"{address}{stype}{k}";
return $"{address}{key}";
}
public void Close()
@ -343,7 +348,7 @@ public partial class Pool : IFrostFSClient
// close all clients
foreach (var innerPool in InnerPools)
foreach (var client in innerPool.Clients)
if (client.StatusMonitor.IsDialed())
if (client.IsDialed())
client.Client?.Close();
}
}
@ -355,7 +360,7 @@ public partial class Pool : IFrostFSClient
for (int i = 0; i < RebalanceParams.NodesParams.Length; i++)
{
var parameters = this.RebalanceParams.NodesParams[i];
var parameters = RebalanceParams.NodesParams[i];
buffers[i] = new double[parameters.Weights.Count];
Task.Run(async () =>
@ -405,25 +410,27 @@ public partial class Pool : IFrostFSClient
try
{
// check timeout settings
changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false);
changed = await client!.RestartIfUnhealthy(ctx).ConfigureAwait(false);
healthy = true;
bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
}
// TODO: specify
catch (FrostFsException e)
{
error = e.Message;
bufferWeights[j] = 0;
Cache.DeleteByPrefix(client.StatusMonitor.Address);
SessionCache.DeleteByPrefix(client.Address);
}
if (changed)
{
StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}");
if (string.IsNullOrEmpty(error))
if (!string.IsNullOrEmpty(error))
{
fields.Append($", reason {error}");
Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString());
if (logger != null)
{
FrostFsMessages.HealthChanged(logger, client.Address, healthy, error!);
}
Interlocked.Exchange(ref healthyChanged, 1);
}
@ -443,6 +450,8 @@ public partial class Pool : IFrostFSClient
}
}
// TODO: remove
private bool CheckSessionTokenErr(Exception error, string address)
{
if (error == null)
@ -452,7 +461,7 @@ public partial class Pool : IFrostFSClient
if (error is SessionNotFoundException || error is SessionExpiredException)
{
this.Cache.DeleteByPrefix(address);
this.SessionCache.DeleteByPrefix(address);
return true;
}
@ -463,14 +472,13 @@ public partial class Pool : IFrostFSClient
{
if (InnerPools == null)
{
throw new InvalidObjectException(nameof(Pool));
throw new FrostFsInvalidObjectException(nameof(Pool));
}
var statistics = new Statistic();
foreach (var inner in InnerPools)
{
int nodeIndex = 0;
int valueIndex = 0;
var nodes = new string[inner.Clients.Length];
@ -478,20 +486,22 @@ public partial class Pool : IFrostFSClient
{
foreach (var client in inner.Clients)
{
if (client.StatusMonitor.IsHealthy())
if (client.IsHealthy())
{
nodes[valueIndex++] = client.StatusMonitor.Address;
nodes[valueIndex] = client.Address;
}
var node = new NodeStatistic
{
Address = client.StatusMonitor.Address,
Methods = client.StatusMonitor.MethodsStatus(),
OverallErrors = client.StatusMonitor.GetOverallErrorRate(),
CurrentErrors = client.StatusMonitor.GetCurrentErrorRate()
Address = client.Address,
Methods = client.MethodsStatus(),
OverallErrors = client.GetOverallErrorRate(),
CurrentErrors = client.GetCurrentErrorRate()
};
statistics.Nodes[nodeIndex++] = node;
statistics.Nodes.Add(node);
valueIndex++;
statistics.OverallErrors += node.OverallErrors;
}
@ -508,120 +518,234 @@ public partial class Pool : IFrostFSClient
public async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
var client = Сonnection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args = null)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetNodeInfoAsync(args).ConfigureAwait(false);
var client = Сonnection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false);
}
public async Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false);
var client = Сonnection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.CreateSessionAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.CreateSessionAsync(args).ConfigureAwait(false);
}
public async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.AddChainAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.AddChainAsync(args).ConfigureAwait(false);
}
public async Task RemoveChainAsync(PrmApeChainRemove args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
await client.RemoveChainAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
await client.Client!.RemoveChainAsync(args).ConfigureAwait(false);
}
public async Task<Chain[]> ListChainAsync(PrmApeChainList args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.ListChainAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.ListChainAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetContainerAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetContainerAsync(args).ConfigureAwait(false);
}
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return client.ListContainersAsync(args);
var client = Сonnection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
return client.Client!.ListContainersAsync(args);
}
public async Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.CreateContainerAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.CreateContainerAsync(args).ConfigureAwait(false);
}
public async Task DeleteContainerAsync(PrmContainerDelete args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
await client.DeleteContainerAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
await client.Client!.DeleteContainerAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetObjectHeadAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetObjectHeadAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsObject> GetObjectAsync(PrmObjectGet args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetObjectAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.GetObjectAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.PutObjectAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.PutObjectAsync(args).ConfigureAwait(false);
}
public async Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.PutSingleObjectAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false);
}
public async Task DeleteObjectAsync(PrmObjectDelete args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
await client.DeleteObjectAsync(args).ConfigureAwait(false);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
await client.Client!.DeleteObjectAsync(args).ConfigureAwait(false);
}
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return client.SearchObjectsAsync(args);
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var client = Сonnection();
args.Context.PoolErrorHandler = client.HandleError;
return client.Client!.SearchObjectsAsync(args);
}
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args = null)
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args)
{
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
return await client.GetBalanceAsync(args).ConfigureAwait(false);
}
var client = Сonnection();
public bool RestartIfUnhealthy(CallContext ctx)
{
throw new NotImplementedException();
}
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
public bool IsHealthy()
{
throw new NotImplementedException();
return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false);
}
protected virtual void Dispose(bool disposing)

View file

@ -3,7 +3,7 @@
public class RebalanceParameters(
NodesParam[] nodesParams,
ulong nodeRequestTimeout,
ulong clientRebalanceInterval,
ulong clientRebalanceInterval,
ulong sessionExpirationDuration)
{
public NodesParam[] NodesParams { get; set; } = nodesParams;

View file

@ -3,18 +3,13 @@ using System.Collections;
namespace FrostFS.SDK.ClientV2;
internal struct SessionCache
internal struct SessionCache(ulong sessionExpirationDuration)
{
public SessionCache(ulong sessionExpirationDuration)
{
TokenDuration = sessionExpirationDuration;
}
internal Hashtable Cache { get; } = [];
internal ulong CurrentEpoch { get; set; }
internal ulong TokenDuration { get; set; }
internal ulong TokenDuration { get; set; } = sessionExpirationDuration;
internal void DeleteByPrefix(string prefix)
{

View file

@ -10,7 +10,7 @@ internal sealed class AccountingServiceProvider : ContextAccessor
internal AccountingServiceProvider(
AccountingService.AccountingServiceClient? accountingServiceClient,
EnvironmentContext context)
ClientContext context)
: base(context)
{
_accountingServiceClient = accountingServiceClient;

View file

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using Frostfs.V2.Ape;
@ -9,7 +10,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
{
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context)
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientContext context)
: base(context)
{
_apeManagerServiceClient = apeManagerServiceClient;
@ -18,10 +19,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
AddChainRequest request = new()
{
@ -45,10 +46,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task RemoveChainAsync(PrmApeChainRemove args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
RemoveChainRequest request = new()
{
@ -70,10 +71,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task<Chain[]> ListChainAsync(PrmApeChainList args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
ListChainsRequest request = new()
{

View file

@ -12,20 +12,28 @@ using FrostFS.Session;
namespace FrostFS.SDK.ClientV2;
internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider
internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider
{
readonly SessionProvider sessions = new(envCtx);
private SessionProvider? sessions;
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
{
sessions ??= new(ClientContext);
if (ClientContext.SessionCache.Cache != null &&
ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey))
{
return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey];
}
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
internal async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
{
GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context!);
GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context);
var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
var response = await service.GetAsync(request, null, args.Context.Deadline, args.Context.CancellationToken);
Verifier.CheckResponse(response);
@ -35,13 +43,13 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
internal async IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args)
{
var ctx = args.Context!;
ctx.OwnerId ??= EnvironmentContext.Owner;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.OwnerId ??= ClientContext.Owner;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
if (ctx.OwnerId == null)
throw new InvalidObjectException(nameof(ctx.OwnerId));
throw new ArgumentException(nameof(ctx.OwnerId));
var request = new ListRequest
{
@ -74,11 +82,11 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
grpcContainer.Version ??= ctx.Version?.ToMessage();
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
if (grpcContainer.OwnerId == null)
throw new InvalidObjectException(nameof(grpcContainer.OwnerId));
throw new ArgumentException(nameof(grpcContainer.OwnerId));
if (grpcContainer.Version == null)
throw new InvalidObjectException(nameof(grpcContainer.Version));
throw new ArgumentException(nameof(grpcContainer.Version));
var request = new PutRequest
{
@ -114,7 +122,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
{
var ctx = args.Context!;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new DeleteRequest
{
@ -150,7 +158,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
{
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(ctx), "Key is null");
var request = new GetRequest
{
@ -207,7 +215,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
await Task.Delay(waitParams.PollInterval).ConfigureAwait(false);
}
catch (ResponseException ex)
catch (FrostFsResponseException ex)
{
if (DateTime.UtcNow >= deadLine)
throw new TimeoutException();

View file

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -12,7 +13,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
{
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context)
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientContext context)
: base(context)
{
this.netmapServiceClient = netmapServiceClient;
@ -20,8 +21,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
{
if (EnvironmentContext.NetworkSettings != null)
return EnvironmentContext.NetworkSettings;
if (ClientContext.NetworkSettings != null)
return ClientContext.NetworkSettings;
var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
@ -38,7 +39,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
SetNetworksParam(param, settings);
}
EnvironmentContext.NetworkSettings = settings;
ClientContext.NetworkSettings = settings;
return settings;
}
@ -46,10 +47,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task<FrostFsNodeInfo> GetLocalNodeInfoAsync(PrmNodeInfo args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new LocalNodeInfoRequest
{
@ -59,8 +60,6 @@ internal sealed class NetmapServiceProvider : ContextAccessor
request.AddMetaHeader(args.XHeaders);
request.Sign(ctx.Key);
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
@ -70,10 +69,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(CallContext ctx)
{
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(ctx), "Key is null");
var request = new NetworkInfoRequest();
@ -91,10 +90,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new NetmapSnapshotRequest();

View file

@ -15,30 +15,32 @@ using Google.Protobuf;
namespace FrostFS.SDK.ClientV2;
internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientContext clientCtx)
: ContextAccessor(clientCtx), ISessionProvider
{
private readonly SessionProvider sessions;
private ObjectService.ObjectServiceClient client;
internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env)
: base(env)
{
this.sessions = new(EnvironmentContext);
this.client = client;
}
private SessionProvider? sessions;
private readonly ObjectService.ObjectServiceClient client = client;
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
{
sessions ??= new(ClientContext);
if (ClientContext.SessionCache.Cache != null &&
ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey))
{
return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey];
}
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
internal async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new HeadRequest
{
@ -74,10 +76,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new GetRequest
{
@ -108,10 +110,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
internal async Task DeleteObjectAsync(PrmObjectDelete args)
{
var ctx = args.Context!;
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new DeleteRequest
{
@ -145,7 +147,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var ctx = args.Context!;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var request = new SearchRequest
{
@ -183,10 +185,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
throw new ArgumentNullException(nameof(args));
if (args.Header == null)
throw new ArgumentException(nameof(args.Header));
throw new ArgumentNullException(nameof(args), "Header is null");
if (args.Payload == null)
throw new ArgumentException(nameof(args.Payload));
throw new ArgumentNullException(nameof(args), "Payload is null");
if (args.ClientCut)
return await PutClientCutObject(args).ConfigureAwait(false);
@ -206,7 +208,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var ctx = args.Context!;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx);
@ -254,7 +256,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
if (args.MaxObjectSizeCache == 0)
{
var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx))
.ConfigureAwait(false);
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
@ -306,7 +308,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds);
_ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context }).ConfigureAwait(false);
_ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, args.Context)).ConfigureAwait(false);
var parentHeader = args.Header.GetHeader();
@ -331,7 +333,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
{
var ctx = args.Context!;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
var payload = args.Payload!;
@ -352,7 +354,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
}
else
{
chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
isRentBuffer = true;
}
@ -409,7 +411,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var header = args.Header!;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new ArgumentNullException(nameof(args), "Key is null");
header.OwnerId ??= ctx.OwnerId;
header.Version ??= ctx.Version;

View file

@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context)
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientContext context)
: base(context)
{
_sessionServiceClient = sessionServiceClient;
@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
var ctx = args.Context!;
ctx.OwnerId ??= EnvironmentContext.Owner;
ctx.OwnerId ??= ClientContext.Owner;
var request = new CreateRequest
{

View file

@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
internal class ContextAccessor(EnvironmentContext context)
internal class ContextAccessor(ClientContext context)
{
protected EnvironmentContext EnvironmentContext { get; set; } = context;
protected ClientContext ClientContext { get; set; } = context;
}

View file

@ -7,14 +7,13 @@ internal interface ISessionProvider
ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx);
}
internal sealed class SessionProvider(EnvironmentContext envCtx)
internal sealed class SessionProvider(ClientContext envCtx)
{
// TODO: implement cache for session in the next iteration
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
{
if (args.SessionToken is null)
{
return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx))
.ConfigureAwait(false);
}

View file

@ -2,16 +2,21 @@ using System;
using System.Buffers;
using System.Security.Cryptography;
using FrostFS.SDK.Cryptography;
using Grpc.Net.Client;
namespace FrostFS.SDK.ClientV2;
public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
{
private ArrayPool<byte>? _arrayPool;
private string? sessionKey;
internal FrostFsOwner? Owner { get; } = owner;
internal string? Address { get; } = channel.Target;
internal GrpcChannel Channel { get; private set; } = channel;
internal FrostFsVersion Version { get; } = version;
@ -22,6 +27,21 @@ public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner?
internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null;
internal SessionCache SessionCache { get; set; }
internal string? SessionCacheKey
{
get
{
if (sessionKey == null && Key != null && Address != null)
{
sessionKey = Pool.FormCacheKey(Address, Key.ECDsaKey.PrivateKey().ToString());
}
return sessionKey;
}
}
/// <summary>
/// Custom pool is used for predefined sizes of buffers like grpc chunk
/// </summary>

View file

@ -17,13 +17,13 @@ public sealed class ObjectReader(AsyncServerStreamingCall<GetResponse> call) : I
internal async Task<Object.Object> ReadHeader()
{
if (!await Call.ResponseStream.MoveNext().ConfigureAwait(false))
throw new InvalidOperationException("unexpected end of stream");
throw new FrostFsStreamException("unexpected end of stream");
var response = Call.ResponseStream.Current;
Verifier.CheckResponse(response);
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init)
throw new InvalidOperationException("unexpected message type");
throw new FrostFsStreamException("unexpected message type");
return new Object.Object
{
@ -41,7 +41,7 @@ public sealed class ObjectReader(AsyncServerStreamingCall<GetResponse> call) : I
Verifier.CheckResponse(response);
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk)
throw new InvalidOperationException("unexpected message type");
throw new FrostFsStreamException("unexpected message type");
return response.Body.Chunk.Memory;
}

View file

@ -59,7 +59,7 @@ internal static class ObjectTools
return;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
throw new FrostFsInvalidObjectException(nameof(ctx.Key));
grpcHeader.Split = new Header.Types.Split
{

View file

@ -122,7 +122,7 @@ public static class Verifier
var status = resp.MetaHeader.Status.ToModel();
if (status != null && !status.IsSuccess)
throw new ResponseException(status);
throw new FrostFsResponseException(status);
}
/// <summary>
@ -137,6 +137,6 @@ public static class Verifier
}
if (!request.Verify())
throw new FormatException($"invalid response, type={request.GetType()}");
throw new FrostFsResponseException($"invalid response, type={request.GetType()}");
}
}

View file

@ -0,0 +1,33 @@
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace FrostFS.SDK.SmokeTests;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
Justification = "parameters are provided by GRPC infrastructure")]
public class CallbackInterceptor(Action<string>? callback = null) : 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 response = await call;
callback?.Invoke($"elapsed");
return response;
}
}

View file

@ -1,41 +0,0 @@
using System.Diagnostics;
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace FrostFS.SDK.SmokeTests;
public class MetricsInterceptor() : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
ArgumentNullException.ThrowIfNull(continuation);
using var call = continuation(request, context);
return new AsyncUnaryCall<TResponse>(
HandleUnaryResponse(call),
call.ResponseHeadersAsync,
call.GetStatus,
call.GetTrailers,
call.Dispose);
}
private static async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> call)
{
var watch = new Stopwatch();
watch.Start();
var response = await call.ResponseAsync.ConfigureAwait(false);
watch.Stop();
// Do something with call info
// var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency;
return response;
}
}

View file

@ -28,19 +28,16 @@ public class NetworkTest : NetworkTestsBase
Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
var param = new PrmNetworkSettings();
if (useContext)
{
param.Context = new CallContext
var param = useContext ?
new PrmNetworkSettings(new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Version
};
}
})
: new PrmNetworkSettings();
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
@ -116,12 +113,11 @@ public class NetworkTest : NetworkTestsBase
Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body };
var param = new PrmNetmapSnapshot();
PrmNetmapSnapshot param;
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new CallContext
var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@ -129,6 +125,14 @@ public class NetworkTest : NetworkTestsBase
Key = ECDsaKey,
Version = Version
};
param = new(ctx);
param.XHeaders.Add("headerKey1", "headerValue1");
}
else
{
param = new();
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
@ -208,12 +212,11 @@ public class NetworkTest : NetworkTestsBase
Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body };
var param = new PrmNodeInfo();
PrmNodeInfo param;
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new CallContext
var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@ -221,6 +224,14 @@ public class NetworkTest : NetworkTestsBase
Key = ECDsaKey,
Version = Version
};
param = new(ctx);
param.XHeaders.Add("headerKey1", "headerValue1");
}
else
{
param = new();
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);

View file

@ -32,7 +32,7 @@ public class ObjectTest : ObjectTestsBase
var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx);
var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx });
var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId, ctx));
Assert.NotNull(result);
@ -50,7 +50,7 @@ public class ObjectTest : ObjectTestsBase
[Fact]
public async void PutObjectTest()
{
Mocker.ResultObjectIds.Add(SHA256.HashData([]));
Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
Random rnd = new();
var bytes = new byte[1024];
@ -107,7 +107,7 @@ public class ObjectTest : ObjectTestsBase
rnd.NextBytes(objIds.ElementAt(2));
foreach (var objId in objIds)
Mocker.ResultObjectIds.Add(objId);
Mocker.ResultObjectIds!.Add(objId);
var result = await GetClient().PutObjectAsync(param);

View file

@ -23,16 +23,6 @@ public class PoolSmokeTests : SmokeTestsBase
Key = keyString.LoadWif(),
NodeParams = [new(1, this.url, 100.0f)],
DialOptions = [new()
{
Authority = "",
Block = false,
DisableHealthCheck = false,
DisableRetry = false,
ReturnLastError = true,
Timeout = 30_000_000
}
],
ClientBuilder = null,
GracefulCloseOnSwitchTimeout = 30_000_000,
Logger = null
@ -85,6 +75,44 @@ public class PoolSmokeTests : SmokeTestsBase
Assert.Equal(9, result.Attributes.Count);
}
[Fact]
public async void NodeInfoStatisticsTwoNodesTest()
{
var options = new InitParameters
{
Key = keyString.LoadWif(),
NodeParams = [
new(1, this.url, 100.0f),
new(2, this.url.Replace('0', '1'), 100.0f)
],
ClientBuilder = null,
GracefulCloseOnSwitchTimeout = 30_000_000,
Logger = null
};
using var pool = new Pool(options);
var callbackText = string.Empty;
var ctx = new CallContext
{
Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"
};
var error = await pool.Dial(ctx).ConfigureAwait(true);
Assert.Null(error);
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
var result = await client.GetNodeInfoAsync();
var statistics = pool.Statistic();
Assert.False(string.IsNullOrEmpty(callbackText));
Assert.Contains(" took ", callbackText, StringComparison.Ordinal);
}
[Fact]
public async void NodeInfoStatisticsTest()
{
@ -308,31 +336,29 @@ public class PoolSmokeTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
{
Context = ctx
};
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
var createdContainer = await pool.CreateContainerAsync(createContainerParam);
var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer));
Assert.NotNull(container);
Assert.True(callbackInvoked);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut
var ctx1 = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
};
var param = new PrmObjectPut(ctx1)
{
Header = new FrostFsObjectHeader(
containerId: createdContainer,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
}
ClientCut = false
};
var objectId = await pool.PutObjectAsync(param);
@ -400,19 +426,16 @@ public class PoolSmokeTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
{
Context = ctx
};
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
var container = await pool.CreateContainerAsync(createContainerParam);
var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container, ctx));
Assert.NotNull(containerInfo);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut
var param = new PrmObjectPut(new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) })
{
Header = new FrostFsObjectHeader(
containerId: container,
@ -420,10 +443,6 @@ public class PoolSmokeTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
},
SessionToken = token
};
@ -496,10 +515,11 @@ public class PoolSmokeTests : SmokeTestsBase
var ctx = new CallContext
{
Timeout = TimeSpan.FromSeconds(10),
Interceptors = new([new MetricsInterceptor()])
};
var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
ctx.Interceptors.Add(new CallbackInterceptor());
var container = await pool.GetContainerAsync(new PrmContainerGet(containerId, ctx));
Assert.NotNull(container);
@ -520,7 +540,7 @@ public class PoolSmokeTests : SmokeTestsBase
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
bool hasObject = false;
await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter)))
{
hasObject = true;
@ -551,21 +571,10 @@ public class PoolSmokeTests : SmokeTestsBase
await Cleanup(pool);
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
IAsyncEnumerator<FrostFsContainerId>? enumerator = null;
do
await foreach (var cid in pool.ListContainersAsync())
{
if (deadline <= DateTime.UtcNow)
{
Assert.Fail("Containers exist");
break;
}
enumerator = pool.ListContainersAsync().GetAsyncEnumerator();
await Task.Delay(500);
Assert.Fail($"Container {cid.GetValue()} exist");
}
while (await enumerator!.MoveNextAsync());
}
private static byte[] GetRandomBytes(int size)

View file

@ -14,12 +14,11 @@ public class SessionTest : SessionTestsBase
public async void CreateSessionTest(bool useContext)
{
var exp = 100u;
var param = new PrmSessionCreate(exp);
PrmSessionCreate param;
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new CallContext
var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@ -27,6 +26,14 @@ public class SessionTest : SessionTestsBase
Key = ECDsaKey,
Version = Mocker.Version
};
param = new PrmSessionCreate(exp, ctx);
param.XHeaders.Add("headerKey1", "headerValue1");
}
else
{
param = new PrmSessionCreate(exp);
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);

View file

@ -26,7 +26,7 @@ public class SmokeClientTests : SmokeTestsBase
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
: FrostFSClient.GetInstance(GetOptions(this.url));
PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
PrmBalance? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetBalanceAsync(prm);
}
@ -38,7 +38,7 @@ public class SmokeClientTests : SmokeTestsBase
{
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetNetmapSnapshotAsync(prm);
Assert.True(result.Epoch > 0);
@ -59,9 +59,11 @@ public class SmokeClientTests : SmokeTestsBase
[InlineData(true)]
public async void NodeInfoTest(bool isSingleOnwerClient)
{
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
using var client = isSingleOnwerClient
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
: FrostFSClient.GetInstance(GetOptions(this.url));
PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
PrmNodeInfo? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetNodeInfoAsync(prm);
@ -93,7 +95,7 @@ public class SmokeClientTests : SmokeTestsBase
{
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx };
PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100, Ctx);
var token = await client.CreateSessionAsync(prm);
@ -262,31 +264,27 @@ public class SmokeClientTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
{
Context = ctx
};
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
var createdContainer = await client.CreateContainerAsync(createContainerParam);
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx));
Assert.NotNull(container);
Assert.True(callbackInvoked);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut
var param = new PrmObjectPut(new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
})
{
Header = new FrostFsObjectHeader(
containerId: createdContainer,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
}
ClientCut = false
};
var objectId = await client.PutObjectAsync(param);
@ -348,19 +346,19 @@ public class SmokeClientTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
{
Context = ctx
};
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
var container = await client.CreateContainerAsync(createContainerParam);
var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container, ctx));
Assert.NotNull(containerInfo);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut
var param = new PrmObjectPut(new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
})
{
Header = new FrostFsObjectHeader(
containerId: container,
@ -368,10 +366,6 @@ public class SmokeClientTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
},
SessionToken = token
};
@ -437,11 +431,12 @@ public class SmokeClientTests : SmokeTestsBase
var ctx = new CallContext
{
Timeout = TimeSpan.FromSeconds(10),
Interceptors = new([new MetricsInterceptor()])
Timeout = TimeSpan.FromSeconds(10)
};
var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
ctx.Interceptors.Add(new CallbackInterceptor());
var container = await client.GetContainerAsync(new PrmContainerGet(containerId, ctx));
Assert.NotNull(container);
@ -462,7 +457,7 @@ public class SmokeClientTests : SmokeTestsBase
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
bool hasObject = false;
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter)))
{
hasObject = true;
@ -510,6 +505,38 @@ public class SmokeClientTests : SmokeTestsBase
while (await enumerator!.MoveNextAsync());
}
[Fact]
public async void NodeInfoCallbackAndInterceptorTest()
{
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
bool callbackInvoked = false;
bool intercepterInvoked = false;
var ctx = new CallContext
{
Callback = new((CallStatistics cs) =>
{
callbackInvoked = true;
Assert.True(cs.ElapsedMicroSeconds > 0);
})
};
ctx.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true));
var result = await client.GetNodeInfoAsync(new PrmNodeInfo(ctx));
Assert.True(callbackInvoked);
Assert.True(intercepterInvoked);
Assert.Equal(2, result.Version.Major);
Assert.Equal(13, result.Version.Minor);
Assert.Equal(NodeState.Online, result.State);
Assert.Equal(33, result.PublicKey.Length);
Assert.Single(result.Addresses);
Assert.Equal(9, result.Attributes.Count);
}
private static byte[] GetRandomBytes(int size)
{
Random rnd = new();