[#24] Client: Implement pool part2
Signed-off-by: Pavel Gross <p.gross@yadro.com>
This commit is contained in:
parent
c9a75ea025
commit
ee20798379
63 changed files with 801 additions and 526 deletions
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,10 +22,10 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
<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="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
|
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)
|
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
||||||
{
|
{
|
||||||
|
@ -93,7 +93,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
var ecdsaKey = settings.Value.Key.LoadWif();
|
var ecdsaKey = settings.Value.Key.LoadWif();
|
||||||
FrostFsOwner.FromKey(ecdsaKey);
|
FrostFsOwner.FromKey(ecdsaKey);
|
||||||
|
|
||||||
ClientCtx = new EnvironmentContext(
|
ClientCtx = new ClientContext(
|
||||||
client: this,
|
client: this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
@ -108,13 +108,13 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private FrostFSClient(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
|
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();
|
clientSettings.Validate();
|
||||||
|
|
||||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||||
|
|
||||||
ClientCtx = new EnvironmentContext(
|
ClientCtx = new ClientContext(
|
||||||
this,
|
this,
|
||||||
key: null,
|
key: null,
|
||||||
owner: null,
|
owner: null,
|
||||||
|
@ -127,7 +127,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private FrostFSClient(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
|
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();
|
clientSettings.Validate();
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||||
|
|
||||||
ClientCtx = new EnvironmentContext(
|
ClientCtx = new ClientContext(
|
||||||
this,
|
this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
@ -146,14 +146,17 @@ public class FrostFSClient : IFrostFSClient
|
||||||
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
// 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,
|
client: this,
|
||||||
key: prm.Key,
|
key: prm.Key,
|
||||||
owner: FrostFsOwner.FromKey(prm.Key!),
|
owner: FrostFsOwner.FromKey(prm.Key!),
|
||||||
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
|
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
|
||||||
version: new FrostFsVersion(2, 13));
|
version: new FrostFsVersion(2, 13))
|
||||||
|
{
|
||||||
|
SessionCache = cache
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -363,10 +366,10 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
|
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
|
||||||
{
|
{
|
||||||
var args = new PrmNodeInfo { Context = ctx };
|
var args = new PrmNodeInfo(ctx);
|
||||||
|
|
||||||
if (ctx?.Version == null)
|
if (ctx?.Version == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Version));
|
throw new ArgumentNullException(nameof(ctx), "Version must be initialized");
|
||||||
|
|
||||||
var service = GetNetmapService(args);
|
var service = GetNetmapService(args);
|
||||||
var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false);
|
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)
|
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)
|
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;
|
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
|
||||||
|
@ -404,24 +405,23 @@ public class FrostFSClient : IFrostFSClient
|
||||||
{
|
{
|
||||||
if (ClientCtx.Version == null)
|
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;
|
ctx.Context.Version = ClientCtx.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
CallInvoker? callInvoker = null;
|
CallInvoker? callInvoker = null;
|
||||||
if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var interceptor in ctx.Context.Interceptors)
|
foreach (var interceptor in ctx.Context.Interceptors)
|
||||||
{
|
|
||||||
callInvoker = AddInvoker(callInvoker, interceptor);
|
callInvoker = AddInvoker(callInvoker, interceptor);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.Context.Callback != null)
|
if (ctx.Context.Callback != null)
|
||||||
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
|
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
|
||||||
|
|
||||||
|
if (ctx.Context.PoolErrorHandler != null)
|
||||||
|
callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler));
|
||||||
|
|
||||||
return callInvoker;
|
return callInvoker;
|
||||||
|
|
||||||
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
|
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
|
||||||
|
@ -429,7 +429,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
if (callInvoker == null)
|
if (callInvoker == null)
|
||||||
callInvoker = ClientCtx.Channel.Intercept(interceptor);
|
callInvoker = ClientCtx.Channel.Intercept(interceptor);
|
||||||
else
|
else
|
||||||
callInvoker.Intercept(interceptor);
|
callInvoker = callInvoker.Intercept(interceptor);
|
||||||
|
|
||||||
return callInvoker;
|
return callInvoker;
|
||||||
}
|
}
|
||||||
|
@ -437,7 +437,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private NetmapServiceProvider GetNetmapService(IContext ctx)
|
private NetmapServiceProvider GetNetmapService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = NetmapServiceClient ?? (callInvoker != null
|
var client = NetmapServiceClient ?? (callInvoker != null
|
||||||
? new NetmapService.NetmapServiceClient(callInvoker)
|
? new NetmapService.NetmapServiceClient(callInvoker)
|
||||||
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
||||||
|
@ -447,7 +447,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private SessionServiceProvider GetSessionService(IContext ctx)
|
private SessionServiceProvider GetSessionService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = SessionServiceClient ?? (callInvoker != null
|
var client = SessionServiceClient ?? (callInvoker != null
|
||||||
? new SessionService.SessionServiceClient(callInvoker)
|
? new SessionService.SessionServiceClient(callInvoker)
|
||||||
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
||||||
|
@ -457,7 +457,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
|
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = ApeManagerServiceClient ?? (callInvoker != null
|
var client = ApeManagerServiceClient ?? (callInvoker != null
|
||||||
? new APEManagerService.APEManagerServiceClient(callInvoker)
|
? new APEManagerService.APEManagerServiceClient(callInvoker)
|
||||||
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
|
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
|
||||||
|
@ -467,7 +467,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private AccountingServiceProvider GetAccouningService(IContext ctx)
|
private AccountingServiceProvider GetAccouningService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = AccountingServiceClient ?? (callInvoker != null
|
var client = AccountingServiceClient ?? (callInvoker != null
|
||||||
? new AccountingService.AccountingServiceClient(callInvoker)
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
||||||
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
||||||
|
@ -477,7 +477,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private ContainerServiceProvider GetContainerService(IContext ctx)
|
private ContainerServiceProvider GetContainerService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = ContainerServiceClient ?? (callInvoker != null
|
var client = ContainerServiceClient ?? (callInvoker != null
|
||||||
? new ContainerService.ContainerServiceClient(callInvoker)
|
? new ContainerService.ContainerServiceClient(callInvoker)
|
||||||
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
||||||
|
@ -487,7 +487,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private ObjectServiceProvider GetObjectService(IContext ctx)
|
private ObjectServiceProvider GetObjectService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = ObjectServiceClient ?? (callInvoker != null
|
var client = ObjectServiceClient ?? (callInvoker != null
|
||||||
? new ObjectService.ObjectServiceClient(callInvoker)
|
? new ObjectService.ObjectServiceClient(callInvoker)
|
||||||
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
||||||
|
@ -497,7 +497,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
private AccountingServiceProvider GetAccountService(IContext ctx)
|
private AccountingServiceProvider GetAccountService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupClientContext(ctx);
|
||||||
var client = AccountingServiceClient ?? (callInvoker != null
|
var client = AccountingServiceClient ?? (callInvoker != null
|
||||||
? new AccountingService.AccountingServiceClient(callInvoker)
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
||||||
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
||||||
|
@ -527,20 +527,13 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
public async Task<string?> Dial(CallContext ctx)
|
public async Task<string?> Dial(CallContext ctx)
|
||||||
{
|
{
|
||||||
try
|
var prm = new PrmBalance(ctx);
|
||||||
{
|
|
||||||
var prm = new PrmBalance { Context = ctx };
|
|
||||||
|
|
||||||
var service = GetAccouningService(prm);
|
var service = GetAccouningService(prm);
|
||||||
var balance = await service.GetBallance(prm).ConfigureAwait(false);
|
_ = await service.GetBallance(prm).ConfigureAwait(false);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (FrostFsException ex)
|
|
||||||
{
|
|
||||||
return ex.Message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RestartIfUnhealthy(CallContext ctx)
|
public bool RestartIfUnhealthy(CallContext ctx)
|
||||||
{
|
{
|
||||||
|
|
68
src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs
Normal file
68
src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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 class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
{
|
{
|
||||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
@ -14,11 +16,6 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
ClientInterceptorContext<TRequest, TResponse> context,
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
{
|
{
|
||||||
if (continuation is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(continuation));
|
|
||||||
}
|
|
||||||
|
|
||||||
var call = continuation(request, context);
|
var call = continuation(request, context);
|
||||||
|
|
||||||
return new AsyncUnaryCall<TResponse>(
|
return new AsyncUnaryCall<TResponse>(
|
||||||
|
@ -33,9 +30,6 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
ClientInterceptorContext<TRequest, TResponse> context,
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
|
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
|
||||||
{
|
{
|
||||||
if (continuation is null)
|
|
||||||
throw new ArgumentNullException(nameof(continuation));
|
|
||||||
|
|
||||||
var call = continuation(context);
|
var call = continuation(context);
|
||||||
|
|
||||||
return new AsyncClientStreamingCall<TRequest, TResponse>(
|
return new AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
@ -52,7 +46,7 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
var watch = new Stopwatch();
|
var watch = new Stopwatch();
|
||||||
watch.Start();
|
watch.Start();
|
||||||
|
|
||||||
var response = await call.ResponseAsync.ConfigureAwait(false);
|
var response = await call;
|
||||||
|
|
||||||
watch.Stop();
|
watch.Stop();
|
||||||
|
|
||||||
|
@ -68,7 +62,7 @@ public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
var watch = new Stopwatch();
|
var watch = new Stopwatch();
|
||||||
watch.Start();
|
watch.Start();
|
||||||
|
|
||||||
var response = await call.ResponseAsync.ConfigureAwait(false);
|
var response = await call;
|
||||||
|
|
||||||
watch.Stop();
|
watch.Stop();
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,5 @@ public interface IFrostFSClient : IDisposable
|
||||||
|
|
||||||
public Task<string?> Dial(CallContext ctx);
|
public Task<string?> Dial(CallContext ctx);
|
||||||
|
|
||||||
public bool RestartIfUnhealthy(CallContext ctx);
|
|
||||||
|
|
||||||
public void Close();
|
public void Close();
|
||||||
}
|
}
|
||||||
|
|
24
src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs
Normal file
24
src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs
Normal 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);
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ public class ClientSettings
|
||||||
{
|
{
|
||||||
var errors = CheckFields();
|
var errors = CheckFields();
|
||||||
if (errors != null)
|
if (errors != null)
|
||||||
ThrowException(errors);
|
ThrowSettingsException(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<string>? CheckFields()
|
protected Collection<string>? CheckFields()
|
||||||
|
@ -29,7 +29,7 @@ public class ClientSettings
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void ThrowException(Collection<string> errors)
|
protected static void ThrowSettingsException(Collection<string> errors)
|
||||||
{
|
{
|
||||||
if (errors is null)
|
if (errors is null)
|
||||||
{
|
{
|
||||||
|
@ -55,7 +55,7 @@ public class SingleOwnerClientSettings : ClientSettings
|
||||||
{
|
{
|
||||||
var errors = CheckFields();
|
var errors = CheckFields();
|
||||||
if (errors != null)
|
if (errors != null)
|
||||||
ThrowException(errors);
|
ThrowSettingsException(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected new Collection<string>? CheckFields()
|
protected new Collection<string>? CheckFields()
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class FrostFsContainerId
|
||||||
return this.modelId;
|
return this.modelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidObjectException();
|
throw new FrostFsInvalidObjectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ContainerID ContainerID
|
internal ContainerID ContainerID
|
||||||
|
@ -47,7 +47,7 @@ public class FrostFsContainerId
|
||||||
return this.containerID;
|
return this.containerID;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidObjectException();
|
throw new FrostFsInvalidObjectException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class FrostFsContainerInfo
|
||||||
{
|
{
|
||||||
if (PlacementPolicy == null)
|
if (PlacementPolicy == null)
|
||||||
{
|
{
|
||||||
throw new InvalidObjectException("PlacementPolicy is null");
|
throw new ArgumentNullException("PlacementPolicy is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container = new Container.Container()
|
this.container = new Container.Container()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using FrostFS.SDK.ClientV2;
|
using System;
|
||||||
|
|
||||||
namespace FrostFS.SDK;
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ public class FrostFsObject
|
||||||
public void SetParent(FrostFsObjectHeader largeObjectHeader)
|
public void SetParent(FrostFsObjectHeader largeObjectHeader)
|
||||||
{
|
{
|
||||||
if (Header?.Split == null)
|
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;
|
Header.Split.ParentHeader = largeObjectHeader;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,10 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class CallContext()
|
public class CallContext()
|
||||||
{
|
{
|
||||||
private ReadOnlyCollection<Interceptor>? interceptors;
|
|
||||||
|
|
||||||
private ByteString? publicKeyCache;
|
private ByteString? publicKeyCache;
|
||||||
|
|
||||||
|
internal Action<Exception>? PoolErrorHandler { get; set; }
|
||||||
|
|
||||||
public ECDsa? Key { get; set; }
|
public ECDsa? Key { get; set; }
|
||||||
|
|
||||||
public FrostFsOwner? OwnerId { get; set; }
|
public FrostFsOwner? OwnerId { get; set; }
|
||||||
|
@ -31,11 +31,7 @@ public class CallContext()
|
||||||
|
|
||||||
public Action<CallStatistics>? Callback { get; set; }
|
public Action<CallStatistics>? Callback { get; set; }
|
||||||
|
|
||||||
public ReadOnlyCollection<Interceptor>? Interceptors
|
public Collection<Interceptor> Interceptors { get; } = [];
|
||||||
{
|
|
||||||
get { return this.interceptors; }
|
|
||||||
set { this.interceptors = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByteString? GetPublicKeyCache()
|
public ByteString? GetPublicKeyCache()
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,5 +7,5 @@ public interface IContext
|
||||||
/// callbacks, interceptors.
|
/// callbacks, interceptors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Additional parameters for calling the method</value>
|
/// <value>Additional parameters for calling the method</value>
|
||||||
CallContext? Context { get; set; }
|
CallContext? Context { get; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsChainTarget Target { get; } = target;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsChainTarget Target { get; } = target;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsChainTarget Target { get; } = target;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmBalance() : PrmBase
|
public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class PrmBase(NameValueCollection? xheaders = null) : IContext
|
public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// FrostFS request X-Headers
|
/// FrostFS request X-Headers
|
||||||
|
@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext
|
||||||
public NameValueCollection XHeaders { get; } = xheaders ?? [];
|
public NameValueCollection XHeaders { get; } = xheaders ?? [];
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CallContext? Context { get; set; }
|
public CallContext Context { get; } = ctx ?? new CallContext();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerInfo Container { get; set; } = container;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerId Container { get; set; } = container;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmContainerGetAll() : PrmBase()
|
public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmNetmapSnapshot() : PrmBase
|
public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmNetworkSettings() : PrmBase
|
public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmNodeInfo() : PrmBase
|
public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public sealed class PrmObjectPut : PrmBase, ISessionToken
|
public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Need to provide values like <c>ContainerId</c> and <c>ObjectType</c> to create and object.
|
/// Need to provide values like <c>ContainerId</c> and <c>ObjectType</c> to create and object.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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>
|
/// <summary>
|
||||||
/// Defines container for the search
|
/// Defines container for the search
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public ulong Expiration { get; set; } = expiration;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
public FrostFsObject FrostFsObject { get; set; } = frostFsObject;
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ public class ClientStatusMonitor : IClientStatus
|
||||||
MethodIndex.methodSessionCreate,
|
MethodIndex.methodSessionCreate,
|
||||||
MethodIndex.methodAPEManagerAddChain,
|
MethodIndex.methodAPEManagerAddChain,
|
||||||
MethodIndex.methodAPEManagerRemoveChain,
|
MethodIndex.methodAPEManagerRemoveChain,
|
||||||
MethodIndex.methodAPEManagerListChains,
|
MethodIndex.methodAPEManagerListChains
|
||||||
MethodIndex.methodLast
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static string GetMethodName(MethodIndex index)
|
public static string GetMethodName(MethodIndex index)
|
||||||
|
@ -53,7 +52,7 @@ public class ClientStatusMonitor : IClientStatus
|
||||||
MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
|
MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
|
||||||
MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
|
MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
|
||||||
MethodIndex.methodAPEManagerListChains => "APEManagerListChains",
|
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 readonly ILogger? logger;
|
||||||
private int healthy;
|
private int healthy;
|
||||||
|
|
||||||
public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold)
|
public ClientStatusMonitor(ILogger? logger, string address)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
healthy = (int)HealthyStatus.Healthy;
|
healthy = (int)HealthyStatus.Healthy;
|
||||||
Address = address;
|
|
||||||
ErrorThreshold = errorThreshold;
|
|
||||||
|
|
||||||
|
Address = address;
|
||||||
Methods = new MethodStatus[MethodIndexes.Length];
|
Methods = new MethodStatus[MethodIndexes.Length];
|
||||||
|
|
||||||
for (int i = 0; i < MethodIndexes.Length; i++)
|
for (int i = 0; i < MethodIndexes.Length; i++)
|
||||||
|
@ -79,7 +77,7 @@ public class ClientStatusMonitor : IClientStatus
|
||||||
|
|
||||||
public string Address { get; }
|
public string Address { get; }
|
||||||
|
|
||||||
internal uint ErrorThreshold { get; }
|
internal uint ErrorThreshold { get; set; }
|
||||||
|
|
||||||
public uint CurrentErrorCount { get; set; }
|
public uint CurrentErrorCount { get; set; }
|
||||||
|
|
||||||
|
@ -89,7 +87,8 @@ public class ClientStatusMonitor : IClientStatus
|
||||||
|
|
||||||
public bool IsHealthy()
|
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()
|
public bool IsDialed()
|
||||||
|
@ -124,14 +123,13 @@ public class ClientStatusMonitor : IClientStatus
|
||||||
if (thresholdReached)
|
if (thresholdReached)
|
||||||
{
|
{
|
||||||
SetUnhealthy();
|
SetUnhealthy();
|
||||||
|
|
||||||
CurrentErrorCount = 0;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,36 @@
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
|
// 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();
|
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;
|
WrapperPrm = wrapperPrm;
|
||||||
StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold);
|
ErrorThreshold = wrapperPrm.ErrorThreshold;
|
||||||
|
|
||||||
try
|
sessionCache = pool.SessionCache;
|
||||||
{
|
Client = new FrostFSClient(WrapperPrm, sessionCache);
|
||||||
Client = new FrostFSClient(WrapperPrm);
|
|
||||||
StatusMonitor.SetHealthy();
|
|
||||||
}
|
|
||||||
catch (FrostFsException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal FrostFSClient? Client { get; private set; }
|
internal FrostFSClient? Client { get; private set; }
|
||||||
|
|
||||||
internal WrapperPrm WrapperPrm { get; }
|
internal WrapperPrm WrapperPrm { get; }
|
||||||
|
|
||||||
internal ClientStatusMonitor StatusMonitor { get; }
|
|
||||||
|
|
||||||
internal FrostFSClient? GetClient()
|
internal FrostFSClient? GetClient()
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (StatusMonitor.IsHealthy())
|
if (IsHealthy())
|
||||||
{
|
{
|
||||||
return Client;
|
return Client;
|
||||||
}
|
}
|
||||||
|
@ -44,21 +42,29 @@ public class ClientWrapper
|
||||||
// dial establishes a connection to the server from the FrostFS network.
|
// dial establishes a connection to the server from the FrostFS network.
|
||||||
// Returns an error describing failure reason. If failed, the client
|
// Returns an error describing failure reason. If failed, the client
|
||||||
// SHOULD NOT be used.
|
// 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)
|
await client.Dial(ctx).ConfigureAwait(false);
|
||||||
return "pool client unhealthy";
|
|
||||||
|
|
||||||
var result = await client.Dial(ctx).ConfigureAwait(false);
|
|
||||||
if (!string.IsNullOrEmpty(result))
|
|
||||||
{
|
|
||||||
StatusMonitor.SetUnhealthyOnDial();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
internal void HandleError(Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is FrostFsResponseException responseException && responseException.Status != null)
|
||||||
|
{
|
||||||
|
switch (responseException.Status.Code)
|
||||||
|
{
|
||||||
|
case FrostFsStatusCode.Internal:
|
||||||
|
case FrostFsStatusCode.WrongMagicNumber:
|
||||||
|
case FrostFsStatusCode.SignatureVerificationFailure:
|
||||||
|
case FrostFsStatusCode.NodeUnderMaintenance:
|
||||||
|
IncErrorRate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IncErrorRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ScheduleGracefulClose()
|
private async Task ScheduleGracefulClose()
|
||||||
|
@ -79,31 +85,31 @@ public class ClientWrapper
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
|
var prmNodeInfo = new PrmNodeInfo(ctx);
|
||||||
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (FrostFsException)
|
catch (RpcException)
|
||||||
{
|
{
|
||||||
wasHealthy = true;
|
wasHealthy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if connection is dialed before, to avoid routine/connection leak,
|
// if connection is dialed before, to avoid routine/connection leak,
|
||||||
// pool has to close it and then initialize once again.
|
// pool has to close it and then initialize once again.
|
||||||
if (StatusMonitor.IsDialed())
|
if (IsDialed())
|
||||||
{
|
{
|
||||||
await ScheduleGracefulClose().ConfigureAwait(false);
|
await ScheduleGracefulClose().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
|
#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
|
#pragma warning restore CA2000
|
||||||
|
|
||||||
//TODO: set additioanl params
|
//TODO: set additioanl params
|
||||||
var error = await client.Dial(ctx).ConfigureAwait(false);
|
var error = await client.Dial(ctx).ConfigureAwait(false);
|
||||||
if (!string.IsNullOrEmpty(error))
|
if (!string.IsNullOrEmpty(error))
|
||||||
{
|
{
|
||||||
StatusMonitor.SetUnhealthyOnDial();
|
SetUnhealthyOnDial();
|
||||||
return wasHealthy;
|
return wasHealthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,22 +120,22 @@ public class ClientWrapper
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
|
var prmNodeInfo = new PrmNodeInfo(ctx);
|
||||||
var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (FrostFsException)
|
catch (FrostFsException)
|
||||||
{
|
{
|
||||||
StatusMonitor.SetUnhealthy();
|
SetUnhealthy();
|
||||||
return wasHealthy;
|
return wasHealthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusMonitor.SetHealthy();
|
SetHealthy();
|
||||||
return !wasHealthy;
|
return !wasHealthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void IncRequests(ulong elapsed, MethodIndex method)
|
internal void IncRequests(ulong elapsed, MethodIndex method)
|
||||||
{
|
{
|
||||||
var methodStat = StatusMonitor.Methods[(int)method];
|
var methodStat = Methods[(int)method];
|
||||||
|
|
||||||
methodStat.IncRequests(elapsed);
|
methodStat.IncRequests(elapsed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
@ -24,7 +26,7 @@ public class InitParameters
|
||||||
|
|
||||||
public NodeParam[]? NodeParams { get; set; }
|
public NodeParam[]? NodeParams { get; set; }
|
||||||
|
|
||||||
public DialOptions[]? DialOptions { get; set; }
|
public GrpcChannelOptions[]? DialOptions { get; set; }
|
||||||
|
|
||||||
public Func<string, ClientWrapper>? ClientBuilder { get; set; }
|
public Func<string, ClientWrapper>? ClientBuilder { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ internal sealed class InnerPool
|
||||||
if (Clients.Length == 1)
|
if (Clients.Length == 1)
|
||||||
{
|
{
|
||||||
var client = Clients[0];
|
var client = Clients[0];
|
||||||
if (client.StatusMonitor.IsHealthy())
|
if (client.IsHealthy())
|
||||||
{
|
{
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ internal sealed class InnerPool
|
||||||
{
|
{
|
||||||
int index = Sampler.Next();
|
int index = Sampler.Next();
|
||||||
|
|
||||||
if (Clients[index].StatusMonitor.IsHealthy())
|
if (Clients[index].IsHealthy())
|
||||||
{
|
{
|
||||||
return Clients[index];
|
return Clients[index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -13,6 +12,8 @@ using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
private ECDsa Key { get; set; }
|
private ECDsa Key { get; set; }
|
||||||
|
|
||||||
private byte[] PublicKey { get; }
|
private string PublicKey { get; }
|
||||||
|
|
||||||
private OwnerID? _ownerId;
|
private OwnerID? _ownerId;
|
||||||
private FrostFsOwner? _owner;
|
private FrostFsOwner? _owner;
|
||||||
|
@ -65,7 +66,7 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
|
internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
|
||||||
|
|
||||||
private SessionCache Cache { get; set; }
|
internal SessionCache SessionCache { get; set; }
|
||||||
|
|
||||||
private ulong SessionTokenDuration { get; set; }
|
private ulong SessionTokenDuration { get; set; }
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
private bool disposedValue;
|
private bool disposedValue;
|
||||||
|
|
||||||
private ILogger? Logger { get; set; }
|
private ILogger? logger { get; set; }
|
||||||
|
|
||||||
private ulong MaxObjectSize { get; set; }
|
private ulong MaxObjectSize { get; set; }
|
||||||
|
|
||||||
|
@ -91,20 +92,20 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
if (options.Key == null)
|
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 nodesParams = AdjustNodeParams(options.NodeParams);
|
||||||
|
|
||||||
var cache = new SessionCache(options.SessionExpirationDuration);
|
var cache = new SessionCache(options.SessionExpirationDuration);
|
||||||
|
|
||||||
FillDefaultInitParams(options, cache);
|
FillDefaultInitParams(options, this);
|
||||||
|
|
||||||
Key = options.Key;
|
Key = options.Key;
|
||||||
PublicKey = Key.PublicKey();
|
PublicKey = $"{Key.PublicKey()}";
|
||||||
|
|
||||||
Cache = cache;
|
SessionCache = cache;
|
||||||
Logger = options.Logger;
|
logger = options.Logger;
|
||||||
SessionTokenDuration = options.SessionExpirationDuration;
|
SessionTokenDuration = options.SessionExpirationDuration;
|
||||||
|
|
||||||
RebalanceParams = new RebalanceParameters(
|
RebalanceParams = new RebalanceParameters(
|
||||||
|
@ -148,47 +149,54 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
for (int j = 0; j < nodeParams.Addresses.Count; j++)
|
for (int j = 0; j < nodeParams.Addresses.Count; j++)
|
||||||
{
|
{
|
||||||
var client = ClientBuilder(nodeParams.Addresses[j]);
|
ClientWrapper? client = null;
|
||||||
clients[j] = client;
|
bool dialed = false;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
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)
|
var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var key = FormCacheKey(nodeParams.Addresses[j], Key, false);
|
var key = FormCacheKey(nodeParams.Addresses[j], Key.PrivateKey().ToString());
|
||||||
_ = Cache.Cache[key] = token;
|
_ = SessionCache.Cache[key] = token;
|
||||||
}
|
|
||||||
catch (FrostFsException ex)
|
|
||||||
{
|
|
||||||
client.StatusMonitor.SetUnhealthy();
|
|
||||||
Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})",
|
|
||||||
client.WrapperPrm.Address, ex.Message);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
atLeastOneHealthy = true;
|
atLeastOneHealthy = true;
|
||||||
}
|
}
|
||||||
|
catch (RpcException ex)
|
||||||
|
{
|
||||||
|
if (!dialed)
|
||||||
|
client!.SetUnhealthyOnDial();
|
||||||
|
else
|
||||||
|
client!.SetUnhealthy();
|
||||||
|
|
||||||
|
if (logger != null)
|
||||||
|
{
|
||||||
|
FrostFsMessages.SessionCreationError(logger, client!.WrapperPrm.Address, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FrostFsInvalidObjectException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sampler = new Sampler(nodeParams.Weights.ToArray());
|
var sampler = new Sampler(nodeParams.Weights.ToArray());
|
||||||
|
|
||||||
inner[i] = new InnerPool(sampler, clients);
|
inner[i] = new InnerPool(sampler, clients);
|
||||||
|
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!atLeastOneHealthy)
|
if (!atLeastOneHealthy)
|
||||||
return "at least one node must be healthy";
|
return "At least one node must be healthy";
|
||||||
|
|
||||||
InnerPools = inner;
|
InnerPools = inner;
|
||||||
|
|
||||||
var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
|
var res = await GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false);
|
||||||
|
|
||||||
MaxObjectSize = res.MaxObjectSize;
|
MaxObjectSize = res.MaxObjectSize;
|
||||||
|
|
||||||
|
@ -252,7 +260,7 @@ public partial class Pool : IFrostFSClient
|
||||||
return adjusted;
|
return adjusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache)
|
private static void FillDefaultInitParams(InitParameters parameters, Pool pool)
|
||||||
{
|
{
|
||||||
if (parameters.SessionExpirationDuration == 0)
|
if (parameters.SessionExpirationDuration == 0)
|
||||||
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
|
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
|
||||||
|
@ -275,8 +283,8 @@ public partial class Pool : IFrostFSClient
|
||||||
if (parameters.NodeStreamTimeout <= 0)
|
if (parameters.NodeStreamTimeout <= 0)
|
||||||
parameters.NodeStreamTimeout = defaultStreamTimeout;
|
parameters.NodeStreamTimeout = defaultStreamTimeout;
|
||||||
|
|
||||||
if (cache.TokenDuration == 0)
|
if (parameters.SessionExpirationDuration == 0)
|
||||||
cache.TokenDuration = defaultSessionTokenExpirationDuration;
|
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
|
||||||
|
|
||||||
parameters.ClientBuilder ??= new Func<string, ClientWrapper>((address) =>
|
parameters.ClientBuilder ??= new Func<string, ClientWrapper>((address) =>
|
||||||
{
|
{
|
||||||
|
@ -291,29 +299,29 @@ public partial class Pool : IFrostFSClient
|
||||||
GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
|
GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
|
||||||
};
|
};
|
||||||
|
|
||||||
return new ClientWrapper(wrapperPrm);
|
return new ClientWrapper(wrapperPrm, pool);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrostFSClient? Сonnection()
|
private ClientWrapper Сonnection()
|
||||||
{
|
{
|
||||||
foreach (var pool in InnerPools!)
|
foreach (var pool in InnerPools!)
|
||||||
{
|
{
|
||||||
var client = pool.Connection();
|
var client = pool.Connection();
|
||||||
if (client != null)
|
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)
|
private static async Task<FrostFsSessionToken?> InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut)
|
||||||
{
|
{
|
||||||
var client = cw.Client;
|
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;
|
var epoch = networkInfo.Epoch;
|
||||||
|
|
||||||
|
@ -321,17 +329,14 @@ public partial class Pool : IFrostFSClient
|
||||||
? ulong.MaxValue
|
? ulong.MaxValue
|
||||||
: epoch + duration;
|
: epoch + duration;
|
||||||
|
|
||||||
var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx };
|
var prmSessionCreate = new PrmSessionCreate(exp, ctx);
|
||||||
|
|
||||||
return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false);
|
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;
|
return $"{address}{key}";
|
||||||
var stype = clientCut ? "client" : "server";
|
|
||||||
|
|
||||||
return $"{address}{stype}{k}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
|
@ -343,7 +348,7 @@ public partial class Pool : IFrostFSClient
|
||||||
// close all clients
|
// close all clients
|
||||||
foreach (var innerPool in InnerPools)
|
foreach (var innerPool in InnerPools)
|
||||||
foreach (var client in innerPool.Clients)
|
foreach (var client in innerPool.Clients)
|
||||||
if (client.StatusMonitor.IsDialed())
|
if (client.IsDialed())
|
||||||
client.Client?.Close();
|
client.Client?.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,7 +360,7 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
for (int i = 0; i < RebalanceParams.NodesParams.Length; i++)
|
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];
|
buffers[i] = new double[parameters.Weights.Count];
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
|
@ -405,25 +410,27 @@ public partial class Pool : IFrostFSClient
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// check timeout settings
|
// check timeout settings
|
||||||
changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false);
|
changed = await client!.RestartIfUnhealthy(ctx).ConfigureAwait(false);
|
||||||
healthy = true;
|
healthy = true;
|
||||||
bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
|
bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
|
||||||
}
|
}
|
||||||
|
// TODO: specify
|
||||||
catch (FrostFsException e)
|
catch (FrostFsException e)
|
||||||
{
|
{
|
||||||
error = e.Message;
|
error = e.Message;
|
||||||
bufferWeights[j] = 0;
|
bufferWeights[j] = 0;
|
||||||
|
|
||||||
Cache.DeleteByPrefix(client.StatusMonitor.Address);
|
SessionCache.DeleteByPrefix(client.Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}");
|
if (!string.IsNullOrEmpty(error))
|
||||||
if (string.IsNullOrEmpty(error))
|
|
||||||
{
|
{
|
||||||
fields.Append($", reason {error}");
|
if (logger != null)
|
||||||
Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString());
|
{
|
||||||
|
FrostFsMessages.HealthChanged(logger, client.Address, healthy, error!);
|
||||||
|
}
|
||||||
|
|
||||||
Interlocked.Exchange(ref healthyChanged, 1);
|
Interlocked.Exchange(ref healthyChanged, 1);
|
||||||
}
|
}
|
||||||
|
@ -443,6 +450,8 @@ public partial class Pool : IFrostFSClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
private bool CheckSessionTokenErr(Exception error, string address)
|
private bool CheckSessionTokenErr(Exception error, string address)
|
||||||
{
|
{
|
||||||
if (error == null)
|
if (error == null)
|
||||||
|
@ -452,7 +461,7 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
if (error is SessionNotFoundException || error is SessionExpiredException)
|
if (error is SessionNotFoundException || error is SessionExpiredException)
|
||||||
{
|
{
|
||||||
this.Cache.DeleteByPrefix(address);
|
this.SessionCache.DeleteByPrefix(address);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,14 +472,13 @@ public partial class Pool : IFrostFSClient
|
||||||
{
|
{
|
||||||
if (InnerPools == null)
|
if (InnerPools == null)
|
||||||
{
|
{
|
||||||
throw new InvalidObjectException(nameof(Pool));
|
throw new FrostFsInvalidObjectException(nameof(Pool));
|
||||||
}
|
}
|
||||||
|
|
||||||
var statistics = new Statistic();
|
var statistics = new Statistic();
|
||||||
|
|
||||||
foreach (var inner in InnerPools)
|
foreach (var inner in InnerPools)
|
||||||
{
|
{
|
||||||
int nodeIndex = 0;
|
|
||||||
int valueIndex = 0;
|
int valueIndex = 0;
|
||||||
var nodes = new string[inner.Clients.Length];
|
var nodes = new string[inner.Clients.Length];
|
||||||
|
|
||||||
|
@ -478,20 +486,22 @@ public partial class Pool : IFrostFSClient
|
||||||
{
|
{
|
||||||
foreach (var client in inner.Clients)
|
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
|
var node = new NodeStatistic
|
||||||
{
|
{
|
||||||
Address = client.StatusMonitor.Address,
|
Address = client.Address,
|
||||||
Methods = client.StatusMonitor.MethodsStatus(),
|
Methods = client.MethodsStatus(),
|
||||||
OverallErrors = client.StatusMonitor.GetOverallErrorRate(),
|
OverallErrors = client.GetOverallErrorRate(),
|
||||||
CurrentErrors = client.StatusMonitor.GetCurrentErrorRate()
|
CurrentErrors = client.GetCurrentErrorRate()
|
||||||
};
|
};
|
||||||
|
|
||||||
statistics.Nodes[nodeIndex++] = node;
|
statistics.Nodes.Add(node);
|
||||||
|
|
||||||
|
valueIndex++;
|
||||||
|
|
||||||
statistics.OverallErrors += node.OverallErrors;
|
statistics.OverallErrors += node.OverallErrors;
|
||||||
}
|
}
|
||||||
|
@ -508,120 +518,234 @@ public partial class Pool : IFrostFSClient
|
||||||
|
|
||||||
public async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
|
public async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
var client = Сonnection();
|
||||||
return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
|
|
||||||
|
args ??= new();
|
||||||
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
|
|
||||||
|
return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args = null)
|
public async Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args = null)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
var client = Сonnection();
|
||||||
return await client.GetNodeInfoAsync(args).ConfigureAwait(false);
|
|
||||||
|
args ??= new();
|
||||||
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
|
|
||||||
|
return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
|
public async Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
var client = Сonnection();
|
||||||
return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false);
|
|
||||||
|
args ??= new();
|
||||||
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
|
|
||||||
|
return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.CreateSessionAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.AddChainAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task RemoveChainAsync(PrmApeChainRemove args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
await client.RemoveChainAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.ListChainAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.GetContainerAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
var client = Сonnection();
|
||||||
return client.ListContainersAsync(args);
|
|
||||||
|
args ??= new();
|
||||||
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
|
|
||||||
|
return client.Client!.ListContainersAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
|
public async Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.CreateContainerAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task DeleteContainerAsync(PrmContainerDelete args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
await client.DeleteContainerAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.GetObjectHeadAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<FrostFsObject> GetObjectAsync(PrmObjectGet args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.GetObjectAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.PutObjectAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return await client.PutSingleObjectAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
await client.DeleteObjectAsync(args).ConfigureAwait(false);
|
{
|
||||||
|
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)
|
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args)
|
||||||
{
|
{
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
if (args is null)
|
||||||
return client.SearchObjectsAsync(args);
|
{
|
||||||
|
throw new ArgumentNullException(nameof(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args = null)
|
var client = Сonnection();
|
||||||
{
|
|
||||||
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
return await client.GetBalanceAsync(args).ConfigureAwait(false);
|
|
||||||
|
return client.Client!.SearchObjectsAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RestartIfUnhealthy(CallContext ctx)
|
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var client = Сonnection();
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsHealthy()
|
args ??= new();
|
||||||
{
|
args.Context.PoolErrorHandler = client.HandleError;
|
||||||
throw new NotImplementedException();
|
|
||||||
|
return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
|
|
@ -3,18 +3,13 @@ using System.Collections;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal struct SessionCache
|
internal struct SessionCache(ulong sessionExpirationDuration)
|
||||||
{
|
{
|
||||||
public SessionCache(ulong sessionExpirationDuration)
|
|
||||||
{
|
|
||||||
TokenDuration = sessionExpirationDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Hashtable Cache { get; } = [];
|
internal Hashtable Cache { get; } = [];
|
||||||
|
|
||||||
internal ulong CurrentEpoch { get; set; }
|
internal ulong CurrentEpoch { get; set; }
|
||||||
|
|
||||||
internal ulong TokenDuration { get; set; }
|
internal ulong TokenDuration { get; set; } = sessionExpirationDuration;
|
||||||
|
|
||||||
internal void DeleteByPrefix(string prefix)
|
internal void DeleteByPrefix(string prefix)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ internal sealed class AccountingServiceProvider : ContextAccessor
|
||||||
|
|
||||||
internal AccountingServiceProvider(
|
internal AccountingServiceProvider(
|
||||||
AccountingService.AccountingServiceClient? accountingServiceClient,
|
AccountingService.AccountingServiceClient? accountingServiceClient,
|
||||||
EnvironmentContext context)
|
ClientContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_accountingServiceClient = accountingServiceClient;
|
_accountingServiceClient = accountingServiceClient;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Frostfs.V2.Ape;
|
using Frostfs.V2.Ape;
|
||||||
|
@ -9,7 +10,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
|
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
|
||||||
|
|
||||||
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context)
|
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_apeManagerServiceClient = apeManagerServiceClient;
|
_apeManagerServiceClient = apeManagerServiceClient;
|
||||||
|
@ -18,10 +19,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
internal async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
AddChainRequest request = new()
|
AddChainRequest request = new()
|
||||||
{
|
{
|
||||||
|
@ -45,10 +46,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task RemoveChainAsync(PrmApeChainRemove args)
|
internal async Task RemoveChainAsync(PrmApeChainRemove args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
RemoveChainRequest request = new()
|
RemoveChainRequest request = new()
|
||||||
{
|
{
|
||||||
|
@ -70,10 +71,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
internal async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
ListChainsRequest request = new()
|
ListChainsRequest request = new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,20 +12,28 @@ using FrostFS.Session;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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)
|
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);
|
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
|
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);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -35,13 +43,13 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
internal async IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args)
|
internal async IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.OwnerId ??= EnvironmentContext.Owner;
|
ctx.OwnerId ??= ClientContext.Owner;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
if (ctx.OwnerId == null)
|
if (ctx.OwnerId == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.OwnerId));
|
throw new ArgumentException(nameof(ctx.OwnerId));
|
||||||
|
|
||||||
var request = new ListRequest
|
var request = new ListRequest
|
||||||
{
|
{
|
||||||
|
@ -74,11 +82,11 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
grpcContainer.Version ??= ctx.Version?.ToMessage();
|
grpcContainer.Version ??= ctx.Version?.ToMessage();
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
if (grpcContainer.OwnerId == null)
|
if (grpcContainer.OwnerId == null)
|
||||||
throw new InvalidObjectException(nameof(grpcContainer.OwnerId));
|
throw new ArgumentException(nameof(grpcContainer.OwnerId));
|
||||||
if (grpcContainer.Version == null)
|
if (grpcContainer.Version == null)
|
||||||
throw new InvalidObjectException(nameof(grpcContainer.Version));
|
throw new ArgumentException(nameof(grpcContainer.Version));
|
||||||
|
|
||||||
var request = new PutRequest
|
var request = new PutRequest
|
||||||
{
|
{
|
||||||
|
@ -114,7 +122,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new DeleteRequest
|
var request = new DeleteRequest
|
||||||
{
|
{
|
||||||
|
@ -150,7 +158,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
|
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(ctx), "Key is null");
|
||||||
|
|
||||||
var request = new GetRequest
|
var request = new GetRequest
|
||||||
{
|
{
|
||||||
|
@ -207,7 +215,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
|
|
||||||
await Task.Delay(waitParams.PollInterval).ConfigureAwait(false);
|
await Task.Delay(waitParams.PollInterval).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (ResponseException ex)
|
catch (FrostFsResponseException ex)
|
||||||
{
|
{
|
||||||
if (DateTime.UtcNow >= deadLine)
|
if (DateTime.UtcNow >= deadLine)
|
||||||
throw new TimeoutException();
|
throw new TimeoutException();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
|
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
|
||||||
|
|
||||||
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context)
|
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
this.netmapServiceClient = netmapServiceClient;
|
this.netmapServiceClient = netmapServiceClient;
|
||||||
|
@ -20,8 +21,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
|
|
||||||
internal async Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
|
internal async Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
|
||||||
{
|
{
|
||||||
if (EnvironmentContext.NetworkSettings != null)
|
if (ClientContext.NetworkSettings != null)
|
||||||
return EnvironmentContext.NetworkSettings;
|
return ClientContext.NetworkSettings;
|
||||||
|
|
||||||
var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
|
var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
SetNetworksParam(param, settings);
|
SetNetworksParam(param, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnvironmentContext.NetworkSettings = settings;
|
ClientContext.NetworkSettings = settings;
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +47,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
internal async Task<FrostFsNodeInfo> GetLocalNodeInfoAsync(PrmNodeInfo args)
|
internal async Task<FrostFsNodeInfo> GetLocalNodeInfoAsync(PrmNodeInfo args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new LocalNodeInfoRequest
|
var request = new LocalNodeInfoRequest
|
||||||
{
|
{
|
||||||
|
@ -59,8 +60,6 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
request.AddMetaHeader(args.XHeaders);
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(ctx.Key);
|
request.Sign(ctx.Key);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
@ -70,10 +69,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
|
|
||||||
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(CallContext ctx)
|
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(CallContext ctx)
|
||||||
{
|
{
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(ctx), "Key is null");
|
||||||
|
|
||||||
var request = new NetworkInfoRequest();
|
var request = new NetworkInfoRequest();
|
||||||
|
|
||||||
|
@ -91,10 +90,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
internal async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
|
internal async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new NetmapSnapshotRequest();
|
var request = new NetmapSnapshotRequest();
|
||||||
|
|
||||||
|
|
|
@ -15,30 +15,32 @@ using Google.Protobuf;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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 SessionProvider? sessions;
|
||||||
private ObjectService.ObjectServiceClient client;
|
private readonly ObjectService.ObjectServiceClient client = client;
|
||||||
|
|
||||||
internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env)
|
|
||||||
: base(env)
|
|
||||||
{
|
|
||||||
this.sessions = new(EnvironmentContext);
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
|
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);
|
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
internal async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new HeadRequest
|
var request = new HeadRequest
|
||||||
{
|
{
|
||||||
|
@ -74,10 +76,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new GetRequest
|
var request = new GetRequest
|
||||||
{
|
{
|
||||||
|
@ -108,10 +110,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
internal async Task DeleteObjectAsync(PrmObjectDelete args)
|
internal async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new DeleteRequest
|
var request = new DeleteRequest
|
||||||
{
|
{
|
||||||
|
@ -145,7 +147,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var request = new SearchRequest
|
var request = new SearchRequest
|
||||||
{
|
{
|
||||||
|
@ -183,10 +185,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
throw new ArgumentNullException(nameof(args));
|
throw new ArgumentNullException(nameof(args));
|
||||||
|
|
||||||
if (args.Header == null)
|
if (args.Header == null)
|
||||||
throw new ArgumentException(nameof(args.Header));
|
throw new ArgumentNullException(nameof(args), "Header is null");
|
||||||
|
|
||||||
if (args.Payload == null)
|
if (args.Payload == null)
|
||||||
throw new ArgumentException(nameof(args.Payload));
|
throw new ArgumentNullException(nameof(args), "Payload is null");
|
||||||
|
|
||||||
if (args.ClientCut)
|
if (args.ClientCut)
|
||||||
return await PutClientCutObject(args).ConfigureAwait(false);
|
return await PutClientCutObject(args).ConfigureAwait(false);
|
||||||
|
@ -206,7 +208,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
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);
|
var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx);
|
||||||
|
|
||||||
|
@ -254,7 +256,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
|
|
||||||
if (args.MaxObjectSizeCache == 0)
|
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);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
|
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);
|
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();
|
var parentHeader = args.Header.GetHeader();
|
||||||
|
|
||||||
|
@ -331,7 +333,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
var payload = args.Payload!;
|
var payload = args.Payload!;
|
||||||
|
|
||||||
|
@ -352,7 +354,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
|
chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
|
||||||
isRentBuffer = true;
|
isRentBuffer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,7 +411,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
var header = args.Header!;
|
var header = args.Header!;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||||
|
|
||||||
header.OwnerId ??= ctx.OwnerId;
|
header.OwnerId ??= ctx.OwnerId;
|
||||||
header.Version ??= ctx.Version;
|
header.Version ??= ctx.Version;
|
||||||
|
|
|
@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
|
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
|
||||||
|
|
||||||
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context)
|
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_sessionServiceClient = sessionServiceClient;
|
_sessionServiceClient = sessionServiceClient;
|
||||||
|
@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
ctx.OwnerId ??= EnvironmentContext.Owner;
|
ctx.OwnerId ??= ClientContext.Owner;
|
||||||
|
|
||||||
var request = new CreateRequest
|
var request = new CreateRequest
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,13 @@ internal interface ISessionProvider
|
||||||
ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx);
|
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)
|
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (args.SessionToken is null)
|
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);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,21 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
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 ArrayPool<byte>? _arrayPool;
|
||||||
|
private string? sessionKey;
|
||||||
|
|
||||||
internal FrostFsOwner? Owner { get; } = owner;
|
internal FrostFsOwner? Owner { get; } = owner;
|
||||||
|
|
||||||
|
internal string? Address { get; } = channel.Target;
|
||||||
|
|
||||||
internal GrpcChannel Channel { get; private set; } = channel;
|
internal GrpcChannel Channel { get; private set; } = channel;
|
||||||
|
|
||||||
internal FrostFsVersion Version { get; } = version;
|
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 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>
|
/// <summary>
|
||||||
/// Custom pool is used for predefined sizes of buffers like grpc chunk
|
/// Custom pool is used for predefined sizes of buffers like grpc chunk
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -17,13 +17,13 @@ public sealed class ObjectReader(AsyncServerStreamingCall<GetResponse> call) : I
|
||||||
internal async Task<Object.Object> ReadHeader()
|
internal async Task<Object.Object> ReadHeader()
|
||||||
{
|
{
|
||||||
if (!await Call.ResponseStream.MoveNext().ConfigureAwait(false))
|
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;
|
var response = Call.ResponseStream.Current;
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init)
|
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
|
return new Object.Object
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ public sealed class ObjectReader(AsyncServerStreamingCall<GetResponse> call) : I
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk)
|
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;
|
return response.Body.Chunk.Memory;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ internal static class ObjectTools
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new FrostFsInvalidObjectException(nameof(ctx.Key));
|
||||||
|
|
||||||
grpcHeader.Split = new Header.Types.Split
|
grpcHeader.Split = new Header.Types.Split
|
||||||
{
|
{
|
||||||
|
|
|
@ -122,7 +122,7 @@ public static class Verifier
|
||||||
var status = resp.MetaHeader.Status.ToModel();
|
var status = resp.MetaHeader.Status.ToModel();
|
||||||
|
|
||||||
if (status != null && !status.IsSuccess)
|
if (status != null && !status.IsSuccess)
|
||||||
throw new ResponseException(status);
|
throw new FrostFsResponseException(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -137,6 +137,6 @@ public static class Verifier
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.Verify())
|
if (!request.Verify())
|
||||||
throw new FormatException($"invalid response, type={request.GetType()}");
|
throw new FrostFsResponseException($"invalid response, type={request.GetType()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
33
src/FrostFS.SDK.Tests/CallbackInterceptor.cs
Normal file
33
src/FrostFS.SDK.Tests/CallbackInterceptor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,19 +28,16 @@ public class NetworkTest : NetworkTestsBase
|
||||||
Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
|
Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
|
||||||
Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
|
Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
|
||||||
|
|
||||||
var param = new PrmNetworkSettings();
|
var param = useContext ?
|
||||||
|
new PrmNetworkSettings(new CallContext
|
||||||
if (useContext)
|
|
||||||
{
|
|
||||||
param.Context = new CallContext
|
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
OwnerId = OwnerId,
|
OwnerId = OwnerId,
|
||||||
Key = ECDsaKey,
|
Key = ECDsaKey,
|
||||||
Version = Version
|
Version = Version
|
||||||
};
|
})
|
||||||
}
|
: new PrmNetworkSettings();
|
||||||
|
|
||||||
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
||||||
|
|
||||||
|
@ -116,12 +113,11 @@ public class NetworkTest : NetworkTestsBase
|
||||||
|
|
||||||
Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body };
|
Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body };
|
||||||
|
|
||||||
var param = new PrmNetmapSnapshot();
|
PrmNetmapSnapshot param;
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
var ctx = new CallContext
|
||||||
param.Context = new CallContext
|
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -129,6 +125,14 @@ public class NetworkTest : NetworkTestsBase
|
||||||
Key = ECDsaKey,
|
Key = ECDsaKey,
|
||||||
Version = Version
|
Version = Version
|
||||||
};
|
};
|
||||||
|
|
||||||
|
param = new(ctx);
|
||||||
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
param = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
||||||
|
@ -208,12 +212,11 @@ public class NetworkTest : NetworkTestsBase
|
||||||
|
|
||||||
Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body };
|
Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body };
|
||||||
|
|
||||||
var param = new PrmNodeInfo();
|
PrmNodeInfo param;
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
var ctx = new CallContext
|
||||||
param.Context = new CallContext
|
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -221,6 +224,14 @@ public class NetworkTest : NetworkTestsBase
|
||||||
Key = ECDsaKey,
|
Key = ECDsaKey,
|
||||||
Version = Version
|
Version = Version
|
||||||
};
|
};
|
||||||
|
|
||||||
|
param = new(ctx);
|
||||||
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
param = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
|
|
||||||
var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx);
|
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);
|
Assert.NotNull(result);
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void PutObjectTest()
|
public async void PutObjectTest()
|
||||||
{
|
{
|
||||||
Mocker.ResultObjectIds.Add(SHA256.HashData([]));
|
Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
|
||||||
|
|
||||||
Random rnd = new();
|
Random rnd = new();
|
||||||
var bytes = new byte[1024];
|
var bytes = new byte[1024];
|
||||||
|
@ -107,7 +107,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
rnd.NextBytes(objIds.ElementAt(2));
|
rnd.NextBytes(objIds.ElementAt(2));
|
||||||
|
|
||||||
foreach (var objId in objIds)
|
foreach (var objId in objIds)
|
||||||
Mocker.ResultObjectIds.Add(objId);
|
Mocker.ResultObjectIds!.Add(objId);
|
||||||
|
|
||||||
var result = await GetClient().PutObjectAsync(param);
|
var result = await GetClient().PutObjectAsync(param);
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,6 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
Key = keyString.LoadWif(),
|
Key = keyString.LoadWif(),
|
||||||
|
|
||||||
NodeParams = [new(1, this.url, 100.0f)],
|
NodeParams = [new(1, this.url, 100.0f)],
|
||||||
DialOptions = [new()
|
|
||||||
{
|
|
||||||
Authority = "",
|
|
||||||
Block = false,
|
|
||||||
DisableHealthCheck = false,
|
|
||||||
DisableRetry = false,
|
|
||||||
ReturnLastError = true,
|
|
||||||
Timeout = 30_000_000
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ClientBuilder = null,
|
ClientBuilder = null,
|
||||||
GracefulCloseOnSwitchTimeout = 30_000_000,
|
GracefulCloseOnSwitchTimeout = 30_000_000,
|
||||||
Logger = null
|
Logger = null
|
||||||
|
@ -85,6 +75,44 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
Assert.Equal(9, result.Attributes.Count);
|
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]
|
[Fact]
|
||||||
public async void NodeInfoStatisticsTest()
|
public async void NodeInfoStatisticsTest()
|
||||||
{
|
{
|
||||||
|
@ -308,31 +336,29 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
};
|
};
|
||||||
|
|
||||||
var createContainerParam = new PrmContainerCreate(
|
var createContainerParam = new PrmContainerCreate(
|
||||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
|
||||||
{
|
|
||||||
Context = ctx
|
|
||||||
};
|
|
||||||
|
|
||||||
var createdContainer = await pool.CreateContainerAsync(createContainerParam);
|
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.NotNull(container);
|
||||||
Assert.True(callbackInvoked);
|
Assert.True(callbackInvoked);
|
||||||
|
|
||||||
var bytes = GetRandomBytes(objectSize);
|
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(
|
Header = new FrostFsObjectHeader(
|
||||||
containerId: createdContainer,
|
containerId: createdContainer,
|
||||||
type: FrostFsObjectType.Regular,
|
type: FrostFsObjectType.Regular,
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false
|
||||||
Context = new CallContext
|
|
||||||
{
|
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var objectId = await pool.PutObjectAsync(param);
|
var objectId = await pool.PutObjectAsync(param);
|
||||||
|
@ -400,19 +426,16 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
};
|
};
|
||||||
|
|
||||||
var createContainerParam = new PrmContainerCreate(
|
var createContainerParam = new PrmContainerCreate(
|
||||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
|
||||||
{
|
|
||||||
Context = ctx
|
|
||||||
};
|
|
||||||
|
|
||||||
var container = await pool.CreateContainerAsync(createContainerParam);
|
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);
|
Assert.NotNull(containerInfo);
|
||||||
|
|
||||||
var bytes = GetRandomBytes(objectSize);
|
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(
|
Header = new FrostFsObjectHeader(
|
||||||
containerId: container,
|
containerId: container,
|
||||||
|
@ -420,10 +443,6 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
Context = new CallContext
|
|
||||||
{
|
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
},
|
|
||||||
SessionToken = token
|
SessionToken = token
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -496,10 +515,11 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
var ctx = new CallContext
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10),
|
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);
|
Assert.NotNull(container);
|
||||||
|
|
||||||
|
@ -520,7 +540,7 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
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;
|
hasObject = true;
|
||||||
|
|
||||||
|
@ -551,21 +571,10 @@ public class PoolSmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
await Cleanup(pool);
|
await Cleanup(pool);
|
||||||
|
|
||||||
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
await foreach (var cid in pool.ListContainersAsync())
|
||||||
|
|
||||||
IAsyncEnumerator<FrostFsContainerId>? enumerator = null;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
if (deadline <= DateTime.UtcNow)
|
Assert.Fail($"Container {cid.GetValue()} exist");
|
||||||
{
|
|
||||||
Assert.Fail("Containers exist");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enumerator = pool.ListContainersAsync().GetAsyncEnumerator();
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
|
||||||
while (await enumerator!.MoveNextAsync());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetRandomBytes(int size)
|
private static byte[] GetRandomBytes(int size)
|
||||||
|
|
|
@ -14,12 +14,11 @@ public class SessionTest : SessionTestsBase
|
||||||
public async void CreateSessionTest(bool useContext)
|
public async void CreateSessionTest(bool useContext)
|
||||||
{
|
{
|
||||||
var exp = 100u;
|
var exp = 100u;
|
||||||
var param = new PrmSessionCreate(exp);
|
PrmSessionCreate param;
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
var ctx = new CallContext
|
||||||
param.Context = new CallContext
|
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -27,6 +26,14 @@ public class SessionTest : SessionTestsBase
|
||||||
Key = ECDsaKey,
|
Key = ECDsaKey,
|
||||||
Version = Mocker.Version
|
Version = Mocker.Version
|
||||||
};
|
};
|
||||||
|
|
||||||
|
param = new PrmSessionCreate(exp, ctx);
|
||||||
|
|
||||||
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
param = new PrmSessionCreate(exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
|
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
|
||||||
: FrostFSClient.GetInstance(GetOptions(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);
|
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));
|
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);
|
var result = await client.GetNetmapSnapshotAsync(prm);
|
||||||
|
|
||||||
Assert.True(result.Epoch > 0);
|
Assert.True(result.Epoch > 0);
|
||||||
|
@ -59,9 +59,11 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public async void NodeInfoTest(bool isSingleOnwerClient)
|
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);
|
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));
|
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);
|
var token = await client.CreateSessionAsync(prm);
|
||||||
|
|
||||||
|
@ -262,31 +264,27 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
};
|
};
|
||||||
|
|
||||||
var createContainerParam = new PrmContainerCreate(
|
var createContainerParam = new PrmContainerCreate(
|
||||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
|
||||||
{
|
|
||||||
Context = ctx
|
|
||||||
};
|
|
||||||
|
|
||||||
var createdContainer = await client.CreateContainerAsync(createContainerParam);
|
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.NotNull(container);
|
||||||
Assert.True(callbackInvoked);
|
Assert.True(callbackInvoked);
|
||||||
|
|
||||||
var bytes = GetRandomBytes(objectSize);
|
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(
|
Header = new FrostFsObjectHeader(
|
||||||
containerId: createdContainer,
|
containerId: createdContainer,
|
||||||
type: FrostFsObjectType.Regular,
|
type: FrostFsObjectType.Regular,
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false
|
||||||
Context = new CallContext
|
|
||||||
{
|
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var objectId = await client.PutObjectAsync(param);
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
@ -348,19 +346,19 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
};
|
};
|
||||||
|
|
||||||
var createContainerParam = new PrmContainerCreate(
|
var createContainerParam = new PrmContainerCreate(
|
||||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
|
||||||
{
|
|
||||||
Context = ctx
|
|
||||||
};
|
|
||||||
|
|
||||||
var container = await client.CreateContainerAsync(createContainerParam);
|
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);
|
Assert.NotNull(containerInfo);
|
||||||
|
|
||||||
var bytes = GetRandomBytes(objectSize);
|
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(
|
Header = new FrostFsObjectHeader(
|
||||||
containerId: container,
|
containerId: container,
|
||||||
|
@ -368,10 +366,6 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
Context = new CallContext
|
|
||||||
{
|
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
},
|
|
||||||
SessionToken = token
|
SessionToken = token
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -437,11 +431,12 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
|
|
||||||
var ctx = new CallContext
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10),
|
Timeout = TimeSpan.FromSeconds(10)
|
||||||
Interceptors = new([new MetricsInterceptor()])
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
Assert.NotNull(container);
|
||||||
|
|
||||||
|
@ -462,7 +457,7 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
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;
|
hasObject = true;
|
||||||
|
|
||||||
|
@ -510,6 +505,38 @@ public class SmokeClientTests : SmokeTestsBase
|
||||||
while (await enumerator!.MoveNextAsync());
|
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)
|
private static byte[] GetRandomBytes(int size)
|
||||||
{
|
{
|
||||||
Random rnd = new();
|
Random rnd = new();
|
||||||
|
|
Loading…
Reference in a new issue