From c9a75ea02558b1c53ca0050b35e47f124cbbfbc7 Mon Sep 17 00:00:00 2001
From: Pavel Gross
Date: Mon, 21 Oct 2024 10:48:00 +0300
Subject: [PATCH 1/3] [#24] Client: Implement pool part1
first iteration - base classes and methods
Signed-off-by: Pavel Gross
---
src/FrostFS.SDK.ClientV2/Cache.cs | 23 -
src/FrostFS.SDK.ClientV2/Caches.cs | 22 +
src/FrostFS.SDK.ClientV2/CllientKey.cs | 13 +-
.../Exceptions/SessionExpiredException.cs | 18 +
.../Exceptions/SessionNotFoundException.cs | 18 +
src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 91 ++-
.../GlobalSuppressions.cs | 8 +
.../Interfaces/IFrostFSClient.cs | 16 +-
.../Mappers/ContainerId.cs | 4 +-
src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs | 8 +-
.../Models/Chain/ChainTarget.cs | 93 ++-
.../Models/Chain/FrostFsChain.cs | 61 +-
.../Models/Chain/FrostFsTargetType.cs | 17 +-
.../Models/Session/FrostFsSessionToken.cs | 5 +-
.../Parameters/{Context.cs => CallContext.cs} | 2 +-
.../Parameters/IContext.cs | 2 +-
.../Parameters/PrmBalance.cs | 5 +
.../Parameters/PrmBase.cs | 2 +-
.../Poll/ClientStatusMonitor.cs | 165 +++++
.../Poll/ClientWrapper.cs | 137 ++++
src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs | 16 +
.../Poll/HealthyStatus.cs | 18 +
.../Poll/IClientStatus.cs | 28 +
.../Poll/InitParameters.cs | 34 +
src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs | 47 ++
src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs | 24 +
src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs | 19 +
src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs | 12 +
.../Poll/NodeStatistic.cs | 12 +
src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs | 12 +
src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 651 ++++++++++++++++++
.../Poll/RebalanceParameters.cs | 16 +
src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs | 14 +
src/FrostFS.SDK.ClientV2/Poll/Sampler.cs | 85 +++
src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs | 29 +
src/FrostFS.SDK.ClientV2/Poll/Statistic.cs | 12 +
.../Poll/StatusSnapshot.cs | 8 +
src/FrostFS.SDK.ClientV2/Poll/WorkList.cs | 26 +
src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs | 34 +
.../Services/AccountingServiceProvider.cs | 40 ++
.../{Shared => }/ApeManagerServiceProvider.cs | 10 +-
.../Services/ContainerServiceProvider.cs | 14 +-
.../Services/NetmapServiceProvider.cs | 30 +-
.../Services/ObjectServiceProvider.cs | 30 +-
.../Services/SessionServiceProvider.cs | 6 +-
.../Services/Shared/ContextAccessor.cs | 4 +-
.../Services/Shared/SessionProvider.cs | 9 +-
...ntEnvironment.cs => EnvironmentContext.cs} | 2 +-
.../Tools/NetworkSettings.cs | 4 +
src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 8 +-
.../Tools/RequestSigner.cs | 5 +
.../accounting/Extension.Message.cs | 62 ++
src/FrostFS.SDK.Tests/ContainerTest.cs | 41 +-
src/FrostFS.SDK.Tests/ContainerTestsBase.cs | 40 ++
src/FrostFS.SDK.Tests/GlobalSuppressions.cs | 6 +
src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 6 +-
.../Mocks/AsyncStreamReaderMock.cs | 7 +-
.../Mocks/ClientStreamWriter.cs | 10 +-
.../ContainerServiceBase.cs | 7 +-
.../ContainerServiceMocks/GetContainerMock.cs | 8 +-
src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 13 +-
src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 11 +-
src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 5 +-
src/FrostFS.SDK.Tests/NetworkTest.cs | 84 +--
src/FrostFS.SDK.Tests/NetworkTestsBase.cs | 48 ++
src/FrostFS.SDK.Tests/ObjectTest.cs | 84 +--
src/FrostFS.SDK.Tests/ObjectTestsBase.cs | 59 ++
src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 603 ++++++++++++++++
src/FrostFS.SDK.Tests/SessionTests.cs | 52 +-
src/FrostFS.SDK.Tests/SessionTestsBase.cs | 48 ++
.../{SmokeTests.cs => SmokeClientTests.cs} | 73 +-
src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 18 +-
72 files changed, 2786 insertions(+), 468 deletions(-)
delete mode 100644 src/FrostFS.SDK.ClientV2/Cache.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Caches.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs
create mode 100644 src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs
rename src/FrostFS.SDK.ClientV2/Parameters/{Context.cs => CallContext.cs} (97%)
create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Pool.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Sampler.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Statistic.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/WorkList.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
rename src/FrostFS.SDK.ClientV2/Services/{Shared => }/ApeManagerServiceProvider.cs (90%)
rename src/FrostFS.SDK.ClientV2/Tools/{ClientEnvironment.cs => EnvironmentContext.cs} (87%)
create mode 100644 src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs
create mode 100644 src/FrostFS.SDK.Tests/ContainerTestsBase.cs
create mode 100644 src/FrostFS.SDK.Tests/GlobalSuppressions.cs
create mode 100644 src/FrostFS.SDK.Tests/NetworkTestsBase.cs
create mode 100644 src/FrostFS.SDK.Tests/ObjectTestsBase.cs
create mode 100644 src/FrostFS.SDK.Tests/PoolSmokeTests.cs
create mode 100644 src/FrostFS.SDK.Tests/SessionTestsBase.cs
rename src/FrostFS.SDK.Tests/{SmokeTests.cs => SmokeClientTests.cs} (88%)
diff --git a/src/FrostFS.SDK.ClientV2/Cache.cs b/src/FrostFS.SDK.ClientV2/Cache.cs
deleted file mode 100644
index 19ec83b..0000000
--- a/src/FrostFS.SDK.ClientV2/Cache.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Microsoft.Extensions.Caching.Memory;
-
-namespace FrostFS.SDK.ClientV2
-{
- internal static class Cache
- {
- private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
- {
- // TODO: get from options?
- SizeLimit = 256
- });
-
- private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
- {
- // TODO: get from options?
- SizeLimit = 1024
- });
-
- internal static IMemoryCache Owners => _ownersCache;
-
- internal static IMemoryCache Containers => _containersCache;
- }
-}
diff --git a/src/FrostFS.SDK.ClientV2/Caches.cs b/src/FrostFS.SDK.ClientV2/Caches.cs
new file mode 100644
index 0000000..ae6a290
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Caches.cs
@@ -0,0 +1,22 @@
+using Microsoft.Extensions.Caching.Memory;
+
+namespace FrostFS.SDK.ClientV2;
+
+internal static class Caches
+{
+ private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
+ {
+ // TODO: get from options?
+ SizeLimit = 256
+ });
+
+ private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
+ {
+ // TODO: get from options?
+ SizeLimit = 1024
+ });
+
+ internal static IMemoryCache Owners => _ownersCache;
+
+ internal static IMemoryCache Containers => _containersCache;
+}
diff --git a/src/FrostFS.SDK.ClientV2/CllientKey.cs b/src/FrostFS.SDK.ClientV2/CllientKey.cs
index 5b10485..a7542cb 100644
--- a/src/FrostFS.SDK.ClientV2/CllientKey.cs
+++ b/src/FrostFS.SDK.ClientV2/CllientKey.cs
@@ -4,12 +4,11 @@ using FrostFS.SDK.Cryptography;
using Google.Protobuf;
-namespace FrostFS.SDK.ClientV2
-{
- public class ClientKey(ECDsa key)
- {
- internal ECDsa ECDsaKey { get; } = key;
+namespace FrostFS.SDK.ClientV2;
- internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
- }
+public class ClientKey(ECDsa key)
+{
+ internal ECDsa ECDsaKey { get; } = key;
+
+ internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
}
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs
new file mode 100644
index 0000000..44404dd
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace FrostFS.SDK.ClientV2;
+
+public class SessionExpiredException : FrostFsException
+{
+ public SessionExpiredException()
+ {
+ }
+
+ public SessionExpiredException(string message) : base(message)
+ {
+ }
+
+ public SessionExpiredException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs
new file mode 100644
index 0000000..5ec0a09
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace FrostFS.SDK.ClientV2;
+
+public class SessionNotFoundException : FrostFsException
+{
+ public SessionNotFoundException()
+ {
+ }
+
+ public SessionNotFoundException(string message) : base(message)
+ {
+ }
+
+ public SessionNotFoundException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
index 4a680de..2ca9e96 100644
--- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
+++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
@@ -6,10 +6,12 @@ using System.Threading.Tasks;
using Frostfs.V2.Ape;
using Frostfs.V2.Apemanager;
+using FrostFS.Accounting;
using FrostFS.Container;
using FrostFS.Netmap;
using FrostFS.Object;
using FrostFS.SDK.ClientV2.Interfaces;
+using FrostFS.SDK.ClientV2.Services;
using FrostFS.SDK.Cryptography;
using FrostFS.Session;
@@ -35,7 +37,9 @@ public class FrostFSClient : IFrostFSClient
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
- internal ClientEnvironment ClientCtx { get; set; }
+ internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
+
+ internal EnvironmentContext ClientCtx { get; set; }
public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null)
{
@@ -89,7 +93,7 @@ public class FrostFSClient : IFrostFSClient
var ecdsaKey = settings.Value.Key.LoadWif();
FrostFsOwner.FromKey(ecdsaKey);
- ClientCtx = new ClientEnvironment(
+ ClientCtx = new EnvironmentContext(
client: this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@@ -110,7 +114,7 @@ public class FrostFSClient : IFrostFSClient
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
- ClientCtx = new ClientEnvironment(
+ ClientCtx = new EnvironmentContext(
this,
key: null,
owner: null,
@@ -131,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
- ClientCtx = new ClientEnvironment(
+ ClientCtx = new EnvironmentContext(
this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@@ -142,6 +146,16 @@ public class FrostFSClient : IFrostFSClient
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
}
+ internal FrostFSClient(WrapperPrm prm)
+ {
+ ClientCtx = new EnvironmentContext(
+ client: this,
+ key: prm.Key,
+ owner: FrostFsOwner.FromKey(prm.Key!),
+ channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
+ version: new FrostFsVersion(2, 13));
+ }
+
public void Dispose()
{
Dispose(true);
@@ -305,16 +319,16 @@ public class FrostFSClient : IFrostFSClient
}
#endregion
- #region SessionImplementation
+ #region Session Implementation
public async Task CreateSessionAsync(PrmSessionCreate args)
{
if (args is null)
throw new ArgumentNullException(nameof(args));
var session = await CreateSessionInternalAsync(args).ConfigureAwait(false);
- var token = session.Serialize();
- return new FrostFsSessionToken(token);
+ var token = session.Serialize();
+ return new FrostFsSessionToken(token, session.Body.Id.ToUuid());
}
internal Task CreateSessionInternalAsync(PrmSessionCreate args)
@@ -327,8 +341,18 @@ public class FrostFSClient : IFrostFSClient
}
#endregion
+ #region Accounting Implementation
+ public async Task GetBalanceAsync(PrmBalance? args)
+ {
+ args ??= new PrmBalance();
+
+ var service = GetAccouningService(args);
+ return await service.GetBallance(args).ConfigureAwait(false);
+ }
+ #endregion
+
#region ToolsImplementation
- public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx)
+ public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
{
if (header == null)
throw new ArgumentNullException(nameof(header));
@@ -337,7 +361,7 @@ public class FrostFSClient : IFrostFSClient
}
#endregion
- private async void CheckFrostFsVersionSupport(Context? ctx = default)
+ private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
{
var args = new PrmNodeInfo { Context = ctx };
@@ -359,7 +383,7 @@ public class FrostFSClient : IFrostFSClient
if (isDisposed)
throw new InvalidObjectException("Client is disposed.");
- ctx.Context ??= new Context();
+ ctx.Context ??= new CallContext();
if (ctx.Context.Key == null)
{
@@ -441,6 +465,16 @@ public class FrostFSClient : IFrostFSClient
return new ApeManagerServiceProvider(client, ClientCtx);
}
+ private AccountingServiceProvider GetAccouningService(IContext ctx)
+ {
+ var callInvoker = SetupEnvironment(ctx);
+ var client = AccountingServiceClient ?? (callInvoker != null
+ ? new AccountingService.AccountingServiceClient(callInvoker)
+ : new AccountingService.AccountingServiceClient(ClientCtx.Channel));
+
+ return new AccountingServiceProvider(client, ClientCtx);
+ }
+
private ContainerServiceProvider GetContainerService(IContext ctx)
{
var callInvoker = SetupEnvironment(ctx);
@@ -461,6 +495,16 @@ public class FrostFSClient : IFrostFSClient
return new ObjectServiceProvider(client, ClientCtx);
}
+ private AccountingServiceProvider GetAccountService(IContext ctx)
+ {
+ var callInvoker = SetupEnvironment(ctx);
+ var client = AccountingServiceClient ?? (callInvoker != null
+ ? new AccountingService.AccountingServiceClient(callInvoker)
+ : new AccountingService.AccountingServiceClient(ClientCtx.Channel));
+
+ return new AccountingServiceProvider(client, ClientCtx);
+ }
+
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
{
try
@@ -480,4 +524,31 @@ public class FrostFSClient : IFrostFSClient
throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}");
}
}
+
+ public async Task Dial(CallContext ctx)
+ {
+ try
+ {
+ var prm = new PrmBalance { Context = ctx };
+
+ var service = GetAccouningService(prm);
+ var balance = await service.GetBallance(prm).ConfigureAwait(false);
+
+ return null;
+ }
+ catch (FrostFsException ex)
+ {
+ return ex.Message;
+ }
+ }
+
+ public bool RestartIfUnhealthy(CallContext ctx)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Close()
+ {
+ Dispose();
+ }
}
diff --git a/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs b/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs
new file mode 100644
index 0000000..c87cda6
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "", Scope = "member", Target = "~M:FrostFS.SDK.ClientV2.Sampler.Next~System.Int32")]
diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
index 3af0c0b..193d128 100644
--- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
+++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
@@ -19,7 +19,7 @@ public interface IFrostFSClient : IDisposable
Task CreateSessionAsync(PrmSessionCreate args);
#endregion
- #region ApeMAnager
+ #region ApeManager
Task AddChainAsync(PrmApeChainAdd args);
Task RemoveChainAsync(PrmApeChainRemove args);
@@ -51,7 +51,17 @@ public interface IFrostFSClient : IDisposable
IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args);
#endregion
- #region Tools
- FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx);
+ #region Account
+ Task GetBalanceAsync(PrmBalance? args = null);
#endregion
+
+ #region Tools
+ FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx);
+ #endregion
+
+ public Task Dial(CallContext ctx);
+
+ public bool RestartIfUnhealthy(CallContext ctx);
+
+ public void Close();
}
diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs
index 86449e2..7a5359d 100644
--- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs
+++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs
@@ -24,14 +24,14 @@ public static class ContainerIdMapper
var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model));
- if (!Cache.Containers.TryGetValue(containerId, out ContainerID? message))
+ if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message))
{
message = new ContainerID
{
Value = ByteString.CopyFrom(Base58.Decode(containerId))
};
- Cache.Containers.Set(containerId, message, _oneHourExpiration);
+ Caches.Containers.Set(containerId, message, _oneHourExpiration);
}
return message!;
diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs
index 6f3276d..c2e61e6 100644
--- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs
+++ b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs
@@ -22,14 +22,14 @@ public static class OwnerIdMapper
throw new ArgumentNullException(nameof(model));
}
- if (!Cache.Owners.TryGetValue(model, out OwnerID? message))
+ if (!Caches.Owners.TryGetValue(model, out OwnerID? message))
{
message = new OwnerID
{
Value = ByteString.CopyFrom(model.ToHash())
};
- Cache.Owners.Set(model, message, _oneHourExpiration);
+ Caches.Owners.Set(model, message, _oneHourExpiration);
}
return message!;
@@ -42,11 +42,11 @@ public static class OwnerIdMapper
throw new ArgumentNullException(nameof(message));
}
- if (!Cache.Owners.TryGetValue(message, out FrostFsOwner? model))
+ if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model))
{
model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray()));
- Cache.Owners.Set(message, model, _oneHourExpiration);
+ Caches.Owners.Set(message, model, _oneHourExpiration);
}
return model!;
diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs
index e9ce527..a009df2 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs
@@ -2,62 +2,61 @@
using Frostfs.V2.Ape;
-namespace FrostFS.SDK.ClientV2
+namespace FrostFS.SDK.ClientV2;
+
+public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable
{
- public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable
+ private ChainTarget? chainTarget;
+
+ public FrostFsTargetType Type { get; } = type;
+
+ public string Name { get; } = name;
+
+ internal ChainTarget GetChainTarget()
{
- private ChainTarget? chainTarget;
-
- public FrostFsTargetType Type { get; } = type;
-
- public string Name { get; } = name;
-
- internal ChainTarget GetChainTarget()
+ return chainTarget ??= new ChainTarget
{
- return chainTarget ??= new ChainTarget
- {
- Type = GetTargetType(Type),
- Name = Name
- };
- }
+ Type = GetTargetType(Type),
+ Name = Name
+ };
+ }
- private static TargetType GetTargetType(FrostFsTargetType type)
+ private static TargetType GetTargetType(FrostFsTargetType type)
+ {
+ return type switch
{
- return type switch
- {
- FrostFsTargetType.Undefined => TargetType.Undefined,
- FrostFsTargetType.Namespace => TargetType.Namespace,
- FrostFsTargetType.Container => TargetType.Container,
- FrostFsTargetType.User => TargetType.User,
- FrostFsTargetType.Group => TargetType.Group,
- _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)),
- };
- }
+ FrostFsTargetType.Undefined => TargetType.Undefined,
+ FrostFsTargetType.Namespace => TargetType.Namespace,
+ FrostFsTargetType.Container => TargetType.Container,
+ FrostFsTargetType.User => TargetType.User,
+ FrostFsTargetType.Group => TargetType.Group,
+ _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)),
+ };
+ }
- public override readonly bool Equals(object obj)
- {
- var target = (FrostFsChainTarget)obj;
- return Equals(target);
- }
+ public override readonly bool Equals(object obj)
+ {
+ var target = (FrostFsChainTarget)obj;
+ return Equals(target);
+ }
- public override readonly int GetHashCode()
- {
- return $"{Name}{Type}".GetHashCode();
- }
+ public override readonly int GetHashCode()
+ {
+ return $"{Name}{Type}".GetHashCode();
+ }
- public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
- {
- return left.Equals(right);
- }
+ public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
+ {
+ return left.Equals(right);
+ }
- public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right)
- {
- return !(left == right);
- }
+ public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right)
+ {
+ return !(left == right);
+ }
- public readonly bool Equals(FrostFsChainTarget other)
- {
- return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
- }
+ public readonly bool Equals(FrostFsChainTarget other)
+ {
+ return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
}
}
diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs
index d6b4b1d..1f4d251 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs
@@ -1,42 +1,41 @@
using Google.Protobuf;
-namespace FrostFS.SDK.ClientV2
+namespace FrostFS.SDK.ClientV2;
+
+public struct FrostFsChain(byte[] raw) : System.IEquatable
{
- public struct FrostFsChain(byte[] raw) : System.IEquatable
+ private ByteString? grpcRaw;
+
+ public byte[] Raw { get; } = raw;
+
+ internal ByteString GetRaw()
{
- private ByteString? grpcRaw;
+ return grpcRaw ??= ByteString.CopyFrom(Raw);
+ }
- public byte[] Raw { get; } = raw;
+ public override readonly bool Equals(object obj)
+ {
+ var chain = (FrostFsChain)obj;
+ return Equals(chain);
+ }
- internal ByteString GetRaw()
- {
- return grpcRaw ??= ByteString.CopyFrom(Raw);
- }
+ public override readonly int GetHashCode()
+ {
+ return Raw.GetHashCode();
+ }
- public override readonly bool Equals(object obj)
- {
- var chain = (FrostFsChain)obj;
- return Equals(chain);
- }
+ public static bool operator ==(FrostFsChain left, FrostFsChain right)
+ {
+ return left.Equals(right);
+ }
- public override readonly int GetHashCode()
- {
- return Raw.GetHashCode();
- }
+ public static bool operator !=(FrostFsChain left, FrostFsChain right)
+ {
+ return !(left == right);
+ }
- public static bool operator ==(FrostFsChain left, FrostFsChain right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(FrostFsChain left, FrostFsChain right)
- {
- return !(left == right);
- }
-
- public readonly bool Equals(FrostFsChain other)
- {
- return Raw == other.Raw;
- }
+ public readonly bool Equals(FrostFsChain other)
+ {
+ return Raw == other.Raw;
}
}
diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs
index 03b1521..ed5fa27 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs
@@ -1,11 +1,10 @@
-namespace FrostFS.SDK.ClientV2
+namespace FrostFS.SDK.ClientV2;
+
+public enum FrostFsTargetType
{
- public enum FrostFsTargetType
- {
- Undefined = 0,
- Namespace,
- Container,
- User,
- Group
- }
+ Undefined = 0,
+ Namespace,
+ Container,
+ User,
+ Group
}
diff --git a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs
index 8954780..672754d 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs
@@ -1,6 +1,9 @@
+using System;
+
namespace FrostFS.SDK;
-public class FrostFsSessionToken(byte[] token)
+public class FrostFsSessionToken(byte[] token, Guid id)
{
+ public Guid Id { get; private set; } = id;
public byte[] Token { get; private set; } = token;
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
similarity index 97%
rename from src/FrostFS.SDK.ClientV2/Parameters/Context.cs
rename to src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
index 0e96929..ff4bc27 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
@@ -11,7 +11,7 @@ using Grpc.Core.Interceptors;
namespace FrostFS.SDK.ClientV2;
-public class Context()
+public class CallContext()
{
private ReadOnlyCollection? interceptors;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
index bd333f7..8c6f1df 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
@@ -7,5 +7,5 @@ public interface IContext
/// callbacks, interceptors.
///
/// Additional parameters for calling the method
- Context? Context { get; set; }
+ CallContext? Context { get; set; }
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
new file mode 100644
index 0000000..3a07fde
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
@@ -0,0 +1,5 @@
+namespace FrostFS.SDK.ClientV2;
+
+public sealed class PrmBalance() : PrmBase
+{
+}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
index 2a6e542..79e5e7e 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
@@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext
public NameValueCollection XHeaders { get; } = xheaders ?? [];
///
- public Context? Context { get; set; }
+ public CallContext? Context { get; set; }
}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
new file mode 100644
index 0000000..e864148
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Threading;
+
+using Microsoft.Extensions.Logging;
+
+namespace FrostFS.SDK.ClientV2;
+
+// clientStatusMonitor count error rate and other statistics for connection.
+public class ClientStatusMonitor : IClientStatus
+{
+ private static readonly MethodIndex[] MethodIndexes =
+ [
+ MethodIndex.methodBalanceGet,
+ MethodIndex.methodContainerPut,
+ MethodIndex.methodContainerGet,
+ MethodIndex.methodContainerList,
+ MethodIndex.methodContainerDelete,
+ MethodIndex.methodEndpointInfo,
+ MethodIndex.methodNetworkInfo,
+ MethodIndex.methodNetMapSnapshot,
+ MethodIndex.methodObjectPut,
+ MethodIndex.methodObjectDelete,
+ MethodIndex.methodObjectGet,
+ MethodIndex.methodObjectHead,
+ MethodIndex.methodObjectRange,
+ MethodIndex.methodObjectPatch,
+ MethodIndex.methodSessionCreate,
+ MethodIndex.methodAPEManagerAddChain,
+ MethodIndex.methodAPEManagerRemoveChain,
+ MethodIndex.methodAPEManagerListChains,
+ MethodIndex.methodLast
+ ];
+
+ public static string GetMethodName(MethodIndex index)
+ {
+ return index switch
+ {
+ MethodIndex.methodBalanceGet => "BalanceGet",
+ MethodIndex.methodContainerPut => "ContainerPut",
+ MethodIndex.methodContainerGet => "ContainerGet",
+ MethodIndex.methodContainerList => "ContainerList",
+ MethodIndex.methodContainerDelete => "ContainerDelete",
+ MethodIndex.methodEndpointInfo => "EndpointInfo",
+ MethodIndex.methodNetworkInfo => "NetworkInfo",
+ MethodIndex.methodNetMapSnapshot => "NetMapSnapshot",
+ MethodIndex.methodObjectPut => "ObjectPut",
+ MethodIndex.methodObjectDelete => "ObjectDelete",
+ MethodIndex.methodObjectGet => "ObjectGet",
+ MethodIndex.methodObjectHead => "ObjectHead",
+ MethodIndex.methodObjectRange => "ObjectRange",
+ MethodIndex.methodObjectPatch => "ObjectPatch",
+ MethodIndex.methodSessionCreate => "SessionCreate",
+ MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
+ MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
+ MethodIndex.methodAPEManagerListChains => "APEManagerListChains",
+ _ => throw new NotImplementedException(),
+ };
+ }
+
+ private readonly object _lock = new();
+
+ private readonly ILogger? logger;
+ private int healthy;
+
+ public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold)
+ {
+ this.logger = logger;
+ healthy = (int)HealthyStatus.Healthy;
+ Address = address;
+ ErrorThreshold = errorThreshold;
+
+ Methods = new MethodStatus[MethodIndexes.Length];
+
+ for (int i = 0; i < MethodIndexes.Length; i++)
+ {
+ Methods[i] = new MethodStatus(GetMethodName(MethodIndexes[i]));
+ }
+ }
+
+ public string Address { get; }
+
+ internal uint ErrorThreshold { get; }
+
+ public uint CurrentErrorCount { get; set; }
+
+ public ulong OverallErrorCount { get; set; }
+
+ public MethodStatus[] Methods { get; private set; }
+
+ public bool IsHealthy()
+ {
+ return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
+ }
+
+ public bool IsDialed()
+ {
+ return Interlocked.CompareExchange(ref healthy, -1, -1) != (int)HealthyStatus.UnhealthyOnDial;
+ }
+
+ public void SetHealthy()
+ {
+ Interlocked.Exchange(ref healthy, (int)HealthyStatus.Healthy);
+ }
+ public void SetUnhealthy()
+ {
+ Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnRequest);
+ }
+
+ public void SetUnhealthyOnDial()
+ {
+ Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnDial);
+ }
+
+ public void IncErrorRate()
+ {
+ bool thresholdReached;
+ lock (_lock)
+ {
+ CurrentErrorCount++;
+ OverallErrorCount++;
+
+ thresholdReached = CurrentErrorCount >= ErrorThreshold;
+
+ if (thresholdReached)
+ {
+ SetUnhealthy();
+
+ CurrentErrorCount = 0;
+ }
+ }
+
+ if (thresholdReached)
+ {
+ logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold);
+ }
+ }
+
+ public uint GetCurrentErrorRate()
+ {
+ lock (_lock)
+ {
+ return CurrentErrorCount;
+ }
+ }
+
+ public ulong GetOverallErrorRate()
+ {
+ lock (_lock)
+ {
+ return OverallErrorCount;
+ }
+ }
+
+ public StatusSnapshot[] MethodsStatus()
+ {
+ var result = new StatusSnapshot[Methods.Length];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = Methods[i].Snapshot!;
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
new file mode 100644
index 0000000..3891250
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
@@ -0,0 +1,137 @@
+using System.Threading.Tasks;
+
+namespace FrostFS.SDK.ClientV2;
+
+// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
+public class ClientWrapper
+{
+ private readonly object _lock = new();
+
+ public ClientWrapper(WrapperPrm wrapperPrm)
+ {
+ WrapperPrm = wrapperPrm;
+ StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold);
+
+ try
+ {
+ Client = new FrostFSClient(WrapperPrm);
+ StatusMonitor.SetHealthy();
+ }
+ catch (FrostFsException)
+ {
+ }
+ }
+
+ internal FrostFSClient? Client { get; private set; }
+
+ internal WrapperPrm WrapperPrm { get; }
+
+ internal ClientStatusMonitor StatusMonitor { get; }
+
+ internal FrostFSClient? GetClient()
+ {
+ lock (_lock)
+ {
+ if (StatusMonitor.IsHealthy())
+ {
+ return Client;
+ }
+
+ return null;
+ }
+ }
+
+ // dial establishes a connection to the server from the FrostFS network.
+ // Returns an error describing failure reason. If failed, the client
+ // SHOULD NOT be used.
+ internal async Task Dial(CallContext ctx)
+ {
+ var client = GetClient();
+
+ if (client == null)
+ return "pool client unhealthy";
+
+ var result = await client.Dial(ctx).ConfigureAwait(false);
+ if (!string.IsNullOrEmpty(result))
+ {
+ StatusMonitor.SetUnhealthyOnDial();
+ return result;
+ }
+
+ return null;
+ }
+
+ private async Task ScheduleGracefulClose()
+ {
+ if (Client == null)
+ return;
+
+ await Task.Delay((int)WrapperPrm.GracefulCloseOnSwitchTimeout).ConfigureAwait(false);
+
+ Client.Close();
+ }
+
+ // restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy.
+ // Indicating if status was changed by this function call and returns error that caused unhealthy status.
+ internal async Task RestartIfUnhealthy(CallContext ctx)
+ {
+ bool wasHealthy;
+
+ try
+ {
+ var prmNodeInfo = new PrmNodeInfo { Context = ctx };
+ var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
+ return false;
+ }
+ catch (FrostFsException)
+ {
+ wasHealthy = true;
+ }
+
+ // if connection is dialed before, to avoid routine/connection leak,
+ // pool has to close it and then initialize once again.
+ if (StatusMonitor.IsDialed())
+ {
+ await ScheduleGracefulClose().ConfigureAwait(false);
+ }
+
+#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
+ FrostFSClient client = new(WrapperPrm);
+#pragma warning restore CA2000
+
+ //TODO: set additioanl params
+ var error = await client.Dial(ctx).ConfigureAwait(false);
+ if (!string.IsNullOrEmpty(error))
+ {
+ StatusMonitor.SetUnhealthyOnDial();
+ return wasHealthy;
+ }
+
+ lock (_lock)
+ {
+ Client = client;
+ }
+
+ try
+ {
+ var prmNodeInfo = new PrmNodeInfo { Context = ctx };
+ var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
+ }
+ catch (FrostFsException)
+ {
+ StatusMonitor.SetUnhealthy();
+ return wasHealthy;
+ }
+
+ StatusMonitor.SetHealthy();
+ return !wasHealthy;
+ }
+
+ internal void IncRequests(ulong elapsed, MethodIndex method)
+ {
+ var methodStat = StatusMonitor.Methods[(int)method];
+
+ methodStat.IncRequests(elapsed);
+ }
+}
+
diff --git a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
new file mode 100644
index 0000000..813d373
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
@@ -0,0 +1,16 @@
+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; }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs
new file mode 100644
index 0000000..f87c1dd
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs
@@ -0,0 +1,18 @@
+namespace FrostFS.SDK.ClientV2;
+
+// values for healthy status of clientStatusMonitor.
+public enum HealthyStatus
+{
+ // statusUnhealthyOnDial is set when dialing to the endpoint is failed,
+ // so there is no connection to the endpoint, and pool should not close it
+ // before re-establishing connection once again.
+ UnhealthyOnDial,
+
+ // statusUnhealthyOnRequest is set when communication after dialing to the
+ // endpoint is failed due to immediate or accumulated errors, connection is
+ // available and pool should close it before re-establishing connection once again.
+ UnhealthyOnRequest,
+
+ // statusHealthy is set when connection is ready to be used by the pool.
+ Healthy
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs
new file mode 100644
index 0000000..cbf597e
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs
@@ -0,0 +1,28 @@
+namespace FrostFS.SDK.ClientV2;
+
+public interface IClientStatus
+{
+ // isHealthy checks if the connection can handle requests.
+ bool IsHealthy();
+
+ // isDialed checks if the connection was created.
+ bool IsDialed();
+
+ // setUnhealthy marks client as unhealthy.
+ void SetUnhealthy();
+
+ // address return address of endpoint.
+ string Address { get; }
+
+ // currentErrorRate returns current errors rate.
+ // After specific threshold connection is considered as unhealthy.
+ // Pool.startRebalance routine can make this connection healthy again.
+ uint GetCurrentErrorRate();
+
+ // overallErrorRate returns the number of all happened errors.
+ ulong GetOverallErrorRate();
+
+ // methodsStatus returns statistic for all used methods.
+ StatusSnapshot[] MethodsStatus();
+}
+
diff --git a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
new file mode 100644
index 0000000..e05c31a
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Security.Cryptography;
+
+using Microsoft.Extensions.Logging;
+
+namespace FrostFS.SDK.ClientV2;
+
+// InitParameters contains values used to initialize connection Pool.
+public class InitParameters
+{
+ public ECDsa? Key { get; set; }
+
+ public ulong NodeDialTimeout { get; set; }
+
+ public ulong NodeStreamTimeout { get; set; }
+
+ public ulong HealthcheckTimeout { get; set; }
+
+ public ulong ClientRebalanceInterval { get; set; }
+
+ public ulong SessionExpirationDuration { get; set; }
+
+ public uint ErrorThreshold { get; set; }
+
+ public NodeParam[]? NodeParams { get; set; }
+
+ public DialOptions[]? DialOptions { get; set; }
+
+ public Func? ClientBuilder { get; set; }
+
+ public ulong GracefulCloseOnSwitchTimeout { get; set; }
+
+ public ILogger? Logger { get; set; }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
new file mode 100644
index 0000000..a535260
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
@@ -0,0 +1,47 @@
+using FrostFS.SDK.ClientV2;
+
+internal sealed class InnerPool
+{
+ private readonly object _lock = new();
+
+ internal InnerPool(Sampler sampler, ClientWrapper[] clients)
+ {
+ Clients = clients;
+ Sampler = sampler;
+ }
+
+ internal Sampler Sampler { get; set; }
+
+ internal ClientWrapper[] Clients { get; }
+
+ internal ClientWrapper? Connection()
+ {
+ lock (_lock)
+ {
+ if (Clients.Length == 1)
+ {
+ var client = Clients[0];
+ if (client.StatusMonitor.IsHealthy())
+ {
+ return client;
+ }
+ }
+ else
+ {
+ var attempts = 3 * Clients.Length;
+
+ for (int i = 0; i < attempts; i++)
+ {
+ int index = Sampler.Next();
+
+ if (Clients[index].StatusMonitor.IsHealthy())
+ {
+ return Clients[index];
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs b/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs
new file mode 100644
index 0000000..8a7008b
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs
@@ -0,0 +1,24 @@
+namespace FrostFS.SDK.ClientV2;
+
+public enum MethodIndex
+{
+ methodBalanceGet,
+ methodContainerPut,
+ methodContainerGet,
+ methodContainerList,
+ methodContainerDelete,
+ methodEndpointInfo,
+ methodNetworkInfo,
+ methodNetMapSnapshot,
+ methodObjectPut,
+ methodObjectDelete,
+ methodObjectGet,
+ methodObjectHead,
+ methodObjectRange,
+ methodObjectPatch,
+ methodSessionCreate,
+ methodAPEManagerAddChain,
+ methodAPEManagerRemoveChain,
+ methodAPEManagerListChains,
+ methodLast
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs
new file mode 100644
index 0000000..33cad49
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs
@@ -0,0 +1,19 @@
+namespace FrostFS.SDK.ClientV2;
+
+public class MethodStatus(string name)
+{
+ private readonly object _lock = new();
+
+ public string Name { get; } = name;
+
+ public StatusSnapshot Snapshot { get; set; } = new StatusSnapshot();
+
+ internal void IncRequests(ulong elapsed)
+ {
+ lock (_lock)
+ {
+ Snapshot.AllTime += elapsed;
+ Snapshot.AllRequests++;
+ }
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs b/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs
new file mode 100644
index 0000000..f95772b
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs
@@ -0,0 +1,12 @@
+namespace FrostFS.SDK.ClientV2;
+
+// NodeParam groups parameters of remote node.
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")]
+public readonly struct NodeParam(int priority, string address, float weight)
+{
+ public int Priority { get; } = priority;
+
+ public string Address { get; } = address;
+
+ public float Weight { get; } = weight;
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs b/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs
new file mode 100644
index 0000000..d51aa83
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs
@@ -0,0 +1,12 @@
+namespace FrostFS.SDK.ClientV2;
+
+public class NodeStatistic
+{
+ public string? Address { get; internal set; }
+
+ public StatusSnapshot[]? Methods { get; internal set; }
+
+ public ulong OverallErrors { get; internal set; }
+
+ public uint CurrentErrors { get; internal set; }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs b/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs
new file mode 100644
index 0000000..eaeef7c
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs
@@ -0,0 +1,12 @@
+using System.Collections.ObjectModel;
+
+namespace FrostFS.SDK.ClientV2;
+
+public class NodesParam(int priority)
+{
+ public int Priority { get; } = priority;
+
+ public Collection Addresses { get; } = [];
+
+ public Collection Weights { get; } = [];
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
new file mode 100644
index 0000000..a304e93
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
@@ -0,0 +1,651 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Frostfs.V2.Ape;
+
+using FrostFS.Refs;
+using FrostFS.SDK.ClientV2.Interfaces;
+using FrostFS.SDK.ClientV2.Mappers.GRPC;
+using FrostFS.SDK.Cryptography;
+
+using Microsoft.Extensions.Logging;
+
+
+namespace FrostFS.SDK.ClientV2;
+
+public partial class Pool : IFrostFSClient
+{
+ const int defaultSessionTokenExpirationDuration = 100; // in epochs
+
+ const int defaultErrorThreshold = 100;
+
+ const int defaultGracefulCloseOnSwitchTimeout = 10; //Seconds;
+ const int defaultRebalanceInterval = 15; //Seconds;
+ const int defaultHealthcheckTimeout = 4; //Seconds;
+ const int defaultDialTimeout = 5; //Seconds;
+ const int defaultStreamTimeout = 10; //Seconds;
+
+ private readonly object _lock = new();
+
+ private InnerPool[]? InnerPools { get; set; }
+
+ private ECDsa Key { get; set; }
+
+ private byte[] PublicKey { get; }
+
+ private OwnerID? _ownerId;
+ private FrostFsOwner? _owner;
+
+ private FrostFsOwner Owner
+ {
+ get
+ {
+ _owner ??= new FrostFsOwner(Key.PublicKey().PublicKeyToAddress());
+ return _owner;
+ }
+ }
+
+ private OwnerID OwnerId
+ {
+ get
+ {
+ if (_ownerId == null)
+ {
+ _owner = new FrostFsOwner(Key.PublicKey().PublicKeyToAddress());
+ _ownerId = _owner.ToMessage();
+ }
+ return _ownerId;
+ }
+ }
+
+ internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
+
+ private SessionCache Cache { get; set; }
+
+ private ulong SessionTokenDuration { get; set; }
+
+ private RebalanceParameters RebalanceParams { get; set; }
+
+ private Func ClientBuilder;
+
+ private bool disposedValue;
+
+ private ILogger? Logger { get; set; }
+
+ private ulong MaxObjectSize { get; set; }
+
+ public IClientStatus? ClientStatus { get; }
+
+ // NewPool creates connection pool using parameters.
+ public Pool(InitParameters options)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (options.Key == null)
+ {
+ throw new FrostFsException($"Missed required parameter {nameof(options.Key)}");
+ }
+
+ var nodesParams = AdjustNodeParams(options.NodeParams);
+
+ var cache = new SessionCache(options.SessionExpirationDuration);
+
+ FillDefaultInitParams(options, cache);
+
+ Key = options.Key;
+ PublicKey = Key.PublicKey();
+
+ Cache = cache;
+ Logger = options.Logger;
+ SessionTokenDuration = options.SessionExpirationDuration;
+
+ RebalanceParams = new RebalanceParameters(
+ nodesParams.ToArray(),
+ options.HealthcheckTimeout,
+ options.ClientRebalanceInterval,
+ options.SessionExpirationDuration);
+
+ ClientBuilder = options.ClientBuilder!;
+ }
+
+ private void SetupContext(CallContext ctx)
+ {
+ if (ctx == null)
+ {
+ throw new ArgumentNullException(nameof(ctx));
+ }
+
+ ctx.Key ??= Key;
+ }
+
+ // Dial establishes a connection to the servers from the FrostFS network.
+ // It also starts a routine that checks the health of the nodes and
+ // updates the weights of the nodes for balancing.
+ // Returns an error describing failure reason.
+ //
+ // If failed, the Pool SHOULD NOT be used.
+ //
+ // See also InitParameters.SetClientRebalanceInterval.
+ public async Task Dial(CallContext ctx)
+ {
+ SetupContext(ctx);
+
+ var inner = new InnerPool[RebalanceParams.NodesParams.Length];
+
+ bool atLeastOneHealthy = false;
+ int i = 0;
+ foreach (var nodeParams in RebalanceParams.NodesParams)
+ {
+ var clients = new ClientWrapper[nodeParams.Weights.Count];
+
+ for (int j = 0; j < nodeParams.Addresses.Count; j++)
+ {
+ var client = ClientBuilder(nodeParams.Addresses[j]);
+ clients[j] = client;
+
+ var error = await client.Dial(ctx).ConfigureAwait(false);
+ if (!string.IsNullOrEmpty(error))
+ {
+ Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error);
+ continue;
+ }
+
+ try
+ {
+ var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false)
+ .ConfigureAwait(false);
+
+ var key = FormCacheKey(nodeParams.Addresses[j], Key, false);
+ _ = Cache.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;
+ }
+
+ var sampler = new Sampler(nodeParams.Weights.ToArray());
+
+ inner[i] = new InnerPool(sampler, clients);
+ }
+
+ if (!atLeastOneHealthy)
+ return "at least one node must be healthy";
+
+ InnerPools = inner;
+
+ var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
+
+ MaxObjectSize = res.MaxObjectSize;
+
+ StartRebalance(ctx);
+
+ return null;
+ }
+
+ private static IEnumerable AdjustNodeParams(NodeParam[]? nodeParams)
+ {
+ if (nodeParams == null || nodeParams.Length == 0)
+ {
+ throw new ArgumentException("No FrostFS peers configured");
+ }
+
+ Dictionary nodesParamsDict = new(nodeParams.Length);
+ foreach (var nodeParam in nodeParams)
+ {
+ if (!nodesParamsDict.TryGetValue(nodeParam.Priority, out var nodes))
+ {
+ nodes = new NodesParam(nodeParam.Priority);
+ nodesParamsDict[nodeParam.Priority] = nodes;
+ }
+
+ nodes.Addresses.Add(nodeParam.Address);
+ nodes.Weights.Add(nodeParam.Weight);
+ }
+
+ var nodesParams = new List(nodesParamsDict.Count);
+
+ foreach (var key in nodesParamsDict.Keys)
+ {
+ var nodes = nodesParamsDict[key];
+ var newWeights = AdjustWeights([.. nodes.Weights]);
+ nodes.Weights.Clear();
+ foreach (var weight in newWeights)
+ {
+ nodes.Weights.Add(weight);
+ }
+
+ nodesParams.Add(nodes);
+ }
+
+ return nodesParams.OrderBy(n => n.Priority);
+ }
+
+ private static double[] AdjustWeights(double[] weights)
+ {
+ var adjusted = new double[weights.Length];
+
+ var sum = weights.Sum();
+
+ if (sum > 0)
+ {
+ for (int i = 0; i < weights.Length; i++)
+ {
+ adjusted[i] = weights[i] / sum;
+ }
+ }
+
+ return adjusted;
+ }
+
+ private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache)
+ {
+ if (parameters.SessionExpirationDuration == 0)
+ parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
+
+ if (parameters.ErrorThreshold == 0)
+ parameters.ErrorThreshold = defaultErrorThreshold;
+
+ if (parameters.ClientRebalanceInterval <= 0)
+ parameters.ClientRebalanceInterval = defaultRebalanceInterval;
+
+ if (parameters.GracefulCloseOnSwitchTimeout <= 0)
+ parameters.GracefulCloseOnSwitchTimeout = defaultGracefulCloseOnSwitchTimeout;
+
+ if (parameters.HealthcheckTimeout <= 0)
+ parameters.HealthcheckTimeout = defaultHealthcheckTimeout;
+
+ if (parameters.NodeDialTimeout <= 0)
+ parameters.NodeDialTimeout = defaultDialTimeout;
+
+ if (parameters.NodeStreamTimeout <= 0)
+ parameters.NodeStreamTimeout = defaultStreamTimeout;
+
+ if (cache.TokenDuration == 0)
+ cache.TokenDuration = defaultSessionTokenExpirationDuration;
+
+ parameters.ClientBuilder ??= new Func((address) =>
+ {
+ var wrapperPrm = new WrapperPrm
+ {
+ Address = address,
+ Key = parameters.Key,
+ Logger = parameters.Logger,
+ DialTimeout = parameters.NodeDialTimeout,
+ StreamTimeout = parameters.NodeStreamTimeout,
+ ErrorThreshold = parameters.ErrorThreshold,
+ GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
+ };
+
+ return new ClientWrapper(wrapperPrm);
+ }
+ );
+ }
+
+ private FrostFSClient? Сonnection()
+ {
+ foreach (var pool in InnerPools!)
+ {
+ var client = pool.Connection();
+ if (client != null)
+ {
+ return client.Client;
+ }
+ }
+
+ return null;
+ }
+
+ private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut)
+ {
+ var client = cw.Client;
+ var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
+
+ var epoch = networkInfo.Epoch;
+
+ ulong exp = ulong.MaxValue - epoch < duration
+ ? ulong.MaxValue
+ : epoch + duration;
+
+ var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx };
+
+ return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false);
+ }
+
+ private static string FormCacheKey(string address, ECDsa key, bool clientCut)
+ {
+ var k = key.PrivateKey;
+ var stype = clientCut ? "client" : "server";
+
+ return $"{address}{stype}{k}";
+ }
+
+ public void Close()
+ {
+ CancellationTokenSource.Cancel();
+
+ if (InnerPools != null)
+ {
+ // close all clients
+ foreach (var innerPool in InnerPools)
+ foreach (var client in innerPool.Clients)
+ if (client.StatusMonitor.IsDialed())
+ client.Client?.Close();
+ }
+ }
+
+ // startRebalance runs loop to monitor connection healthy status.
+ internal void StartRebalance(CallContext ctx)
+ {
+ var buffers = new double[RebalanceParams.NodesParams.Length][];
+
+ for (int i = 0; i < RebalanceParams.NodesParams.Length; i++)
+ {
+ var parameters = this.RebalanceParams.NodesParams[i];
+ buffers[i] = new double[parameters.Weights.Count];
+
+ Task.Run(async () =>
+ {
+ await Task.Delay((int)RebalanceParams.ClientRebalanceInterval).ConfigureAwait(false);
+ UpdateNodesHealth(ctx, buffers);
+ });
+ }
+ }
+
+ private void UpdateNodesHealth(CallContext ctx, double[][] buffers)
+ {
+ var tasks = new Task[InnerPools!.Length];
+
+ for (int i = 0; i < InnerPools.Length; i++)
+ {
+ var bufferWeights = buffers[i];
+
+ tasks[i] = Task.Run(() => UpdateInnerNodesHealth(ctx, i, bufferWeights));
+ }
+
+ Task.WaitAll(tasks);
+ }
+
+ private async ValueTask UpdateInnerNodesHealth(CallContext ctx, int poolIndex, double[] bufferWeights)
+ {
+ if (poolIndex > InnerPools!.Length - 1)
+ {
+ return;
+ }
+
+ var pool = InnerPools[poolIndex];
+
+ var options = RebalanceParams;
+
+ int healthyChanged = 0;
+
+ var tasks = new Task[pool.Clients.Length];
+
+ for (int j = 0; j < pool.Clients.Length; j++)
+ {
+ var client = pool.Clients[j];
+ var healthy = false;
+ string? error = null;
+ var changed = false;
+
+ try
+ {
+ // check timeout settings
+ changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false);
+ healthy = true;
+ bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
+ }
+ catch (FrostFsException e)
+ {
+ error = e.Message;
+ bufferWeights[j] = 0;
+
+ Cache.DeleteByPrefix(client.StatusMonitor.Address);
+ }
+
+ if (changed)
+ {
+ StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}");
+ if (string.IsNullOrEmpty(error))
+ {
+ fields.Append($", reason {error}");
+ Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString());
+
+ Interlocked.Exchange(ref healthyChanged, 1);
+ }
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ if (Interlocked.CompareExchange(ref healthyChanged, -1, -1) == 1)
+ {
+ var probabilities = AdjustWeights(bufferWeights);
+
+ lock (_lock)
+ {
+ pool.Sampler = new Sampler(probabilities);
+ }
+ }
+ }
+ }
+
+ private bool CheckSessionTokenErr(Exception error, string address)
+ {
+ if (error == null)
+ {
+ return false;
+ }
+
+ if (error is SessionNotFoundException || error is SessionExpiredException)
+ {
+ this.Cache.DeleteByPrefix(address);
+ return true;
+ }
+
+ return false;
+ }
+
+ public Statistic Statistic()
+ {
+ if (InnerPools == null)
+ {
+ throw new InvalidObjectException(nameof(Pool));
+ }
+
+ var statistics = new Statistic();
+
+ foreach (var inner in InnerPools)
+ {
+ int nodeIndex = 0;
+ int valueIndex = 0;
+ var nodes = new string[inner.Clients.Length];
+
+ lock (_lock)
+ {
+ foreach (var client in inner.Clients)
+ {
+ if (client.StatusMonitor.IsHealthy())
+ {
+ nodes[valueIndex++] = client.StatusMonitor.Address;
+ }
+
+ var node = new NodeStatistic
+ {
+ Address = client.StatusMonitor.Address,
+ Methods = client.StatusMonitor.MethodsStatus(),
+ OverallErrors = client.StatusMonitor.GetOverallErrorRate(),
+ CurrentErrors = client.StatusMonitor.GetCurrentErrorRate()
+ };
+
+ statistics.Nodes[nodeIndex++] = node;
+
+ statistics.OverallErrors += node.OverallErrors;
+ }
+
+ if (statistics.CurrentNodes == null || statistics.CurrentNodes.Length == 0)
+ {
+ statistics.CurrentNodes = nodes;
+ }
+ }
+ }
+
+ return statistics;
+ }
+
+ public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task GetNodeInfoAsync(PrmNodeInfo? args = null)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetNodeInfoAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task CreateSessionAsync(PrmSessionCreate args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.CreateSessionAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task AddChainAsync(PrmApeChainAdd args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.AddChainAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task RemoveChainAsync(PrmApeChainRemove args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ await client.RemoveChainAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task ListChainAsync(PrmApeChainList args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.ListChainAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task GetContainerAsync(PrmContainerGet args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetContainerAsync(args).ConfigureAwait(false);
+ }
+
+ public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return client.ListContainersAsync(args);
+ }
+
+ public async Task CreateContainerAsync(PrmContainerCreate args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.CreateContainerAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task DeleteContainerAsync(PrmContainerDelete args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ await client.DeleteContainerAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task GetObjectHeadAsync(PrmObjectHeadGet args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetObjectHeadAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task GetObjectAsync(PrmObjectGet args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetObjectAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task PutObjectAsync(PrmObjectPut args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.PutObjectAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task PutSingleObjectAsync(PrmSingleObjectPut args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.PutSingleObjectAsync(args).ConfigureAwait(false);
+ }
+
+ public async Task DeleteObjectAsync(PrmObjectDelete args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ await client.DeleteObjectAsync(args).ConfigureAwait(false);
+ }
+
+ public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return client.SearchObjectsAsync(args);
+ }
+
+ public async Task GetBalanceAsync(PrmBalance? args = null)
+ {
+ var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
+ return await client.GetBalanceAsync(args).ConfigureAwait(false);
+ }
+
+ public bool RestartIfUnhealthy(CallContext ctx)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsHealthy()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ Close();
+ }
+
+ disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
new file mode 100644
index 0000000..b1bc9b9
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
@@ -0,0 +1,16 @@
+namespace FrostFS.SDK.ClientV2;
+
+public class RebalanceParameters(
+ NodesParam[] nodesParams,
+ ulong nodeRequestTimeout,
+ ulong clientRebalanceInterval,
+ ulong sessionExpirationDuration)
+{
+ public NodesParam[] NodesParams { get; set; } = nodesParams;
+
+ public ulong NodeRequestTimeout { get; set; } = nodeRequestTimeout;
+
+ public ulong ClientRebalanceInterval { get; set; } = clientRebalanceInterval;
+
+ public ulong SessionExpirationDuration { get; set; } = sessionExpirationDuration;
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs b/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs
new file mode 100644
index 0000000..9eb931f
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace FrostFS.SDK.ClientV2;
+
+// RequestInfo groups info about pool request.
+struct RequestInfo
+{
+ public string Address { get; set; }
+
+ public MethodIndex MethodIndex { get; set; }
+
+ public TimeSpan Elapsed { get; set; }
+}
+
diff --git a/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs b/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs
new file mode 100644
index 0000000..0d4a1e0
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs
@@ -0,0 +1,85 @@
+using System;
+
+namespace FrostFS.SDK.ClientV2;
+
+internal sealed class Sampler
+{
+ private readonly object _lock = new();
+
+ private Random random = new();
+
+ internal double[] Probabilities { get; set; }
+ internal int[] Alias { get; set; }
+
+ internal Sampler(double[] probabilities)
+ {
+ var small = new WorkList();
+ var large = new WorkList();
+
+ var n = probabilities.Length;
+
+ // sampler.randomGenerator = rand.New(source)
+ Probabilities = new double[n];
+ Alias = new int[n];
+
+ // Compute scaled probabilities.
+ var p = new double[n];
+
+ for (int i = 0; i < n; i++)
+ {
+ p[i] = probabilities[i] * n;
+ if (p[i] < 1)
+ small.Add(i);
+ else
+ large.Add(i);
+ }
+
+ while (small.Length > 0 && large.Length > 0)
+ {
+ var l = small.Remove();
+ var g = large.Remove();
+
+ Probabilities[l] = p[l];
+ Alias[l] = g;
+
+ p[g] = p[g] + p[l] - 1;
+
+ if (p[g] < 1)
+ small.Add(g);
+ else
+ large.Add(g);
+ }
+
+ while (large.Length > 0)
+ {
+ var g = large.Remove();
+ Probabilities[g] = 1;
+ }
+
+ while (small.Length > 0)
+ {
+ var l = small.Remove();
+ probabilities[l] = 1;
+ }
+ }
+
+ internal int Next()
+ {
+ var n = Alias.Length;
+
+ int i;
+ double f;
+ lock (_lock)
+ {
+ i = random.Next(0, n - 1);
+ f = random.NextDouble();
+ }
+
+ if (f < Probabilities[i])
+ {
+ return i;
+ }
+
+ return Alias[i];
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
new file mode 100644
index 0000000..eccb0a5
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections;
+
+namespace FrostFS.SDK.ClientV2;
+
+internal struct SessionCache
+{
+ public SessionCache(ulong sessionExpirationDuration)
+ {
+ TokenDuration = sessionExpirationDuration;
+ }
+
+ internal Hashtable Cache { get; } = [];
+
+ internal ulong CurrentEpoch { get; set; }
+
+ internal ulong TokenDuration { get; set; }
+
+ internal void DeleteByPrefix(string prefix)
+ {
+ foreach (var key in Cache.Keys)
+ {
+ if (((string)key).StartsWith(prefix, StringComparison.Ordinal))
+ {
+ Cache.Remove(key);
+ }
+ }
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs b/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs
new file mode 100644
index 0000000..58fa72d
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs
@@ -0,0 +1,12 @@
+using System.Collections.ObjectModel;
+
+namespace FrostFS.SDK.ClientV2;
+
+public sealed class Statistic
+{
+ public ulong OverallErrors { get; internal set; }
+
+ public Collection Nodes { get; } = [];
+
+ public string[]? CurrentNodes { get; internal set; }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs b/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs
new file mode 100644
index 0000000..9434cb9
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs
@@ -0,0 +1,8 @@
+namespace FrostFS.SDK.ClientV2;
+
+public class StatusSnapshot()
+{
+ public ulong AllTime { get; internal set; }
+
+ public ulong AllRequests { get; internal set; }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs b/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs
new file mode 100644
index 0000000..39551f4
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace FrostFS.SDK.ClientV2;
+
+internal sealed class WorkList
+{
+ private readonly List elements = [];
+
+ internal int Length
+ {
+ get { return elements.Count; }
+ }
+
+ internal void Add(int element)
+ {
+ elements.Add(element);
+ }
+
+ internal int Remove()
+ {
+ int last = elements.LastOrDefault();
+ elements.RemoveAt(elements.Count - 1);
+ return last;
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs b/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs
new file mode 100644
index 0000000..b389f68
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Security.Cryptography;
+
+using Grpc.Net.Client;
+
+using Microsoft.Extensions.Logging;
+
+namespace FrostFS.SDK.ClientV2;
+
+// wrapperPrm is params to create clientWrapper.
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")]
+public struct WrapperPrm
+{
+ internal ILogger? Logger { get; set; }
+
+ internal string Address { get; set; }
+
+ internal ECDsa? Key { get; set; }
+
+ internal ulong DialTimeout { get; set; }
+
+ internal ulong StreamTimeout { get; set; }
+
+ internal uint ErrorThreshold { get; set; }
+
+ internal Action ResponseInfoCallback { get; set; }
+
+ internal Action PoolRequestInfoCallback { get; set; }
+
+ internal GrpcChannelOptions GrpcChannelOptions { get; set; }
+
+ internal ulong GracefulCloseOnSwitchTimeout { get; set; }
+}
+
diff --git a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
new file mode 100644
index 0000000..94dda54
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
@@ -0,0 +1,40 @@
+using System.Threading.Tasks;
+
+using FrostFS.Accounting;
+
+namespace FrostFS.SDK.ClientV2;
+
+internal sealed class AccountingServiceProvider : ContextAccessor
+{
+ private readonly AccountingService.AccountingServiceClient? _accountingServiceClient;
+
+ internal AccountingServiceProvider(
+ AccountingService.AccountingServiceClient? accountingServiceClient,
+ EnvironmentContext context)
+ : base(context)
+ {
+ _accountingServiceClient = accountingServiceClient;
+ }
+
+ internal async Task GetBallance(PrmBalance args)
+ {
+ var ctx = args.Context!;
+
+ BalanceRequest request = new()
+ {
+ Body = new()
+ {
+ OwnerId = ctx.OwnerId!.OwnerID
+ }
+ };
+
+ request.AddMetaHeader(args.XHeaders);
+ request.Sign(ctx.Key!);
+
+ var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.Deadline, ctx.CancellationToken);
+
+ Verifier.CheckResponse(response);
+
+ return response.Body.Balance;
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
similarity index 90%
rename from src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs
rename to src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
index ea92329..1c5ab8c 100644
--- a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
@@ -3,13 +3,13 @@ using System.Threading.Tasks;
using Frostfs.V2.Ape;
using Frostfs.V2.Apemanager;
-namespace FrostFS.SDK.ClientV2;
+namespace FrostFS.SDK.ClientV2.Services;
internal sealed class ApeManagerServiceProvider : ContextAccessor
{
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
- internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientEnvironment context)
+ internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context)
: base(context)
{
_apeManagerServiceClient = apeManagerServiceClient;
@@ -18,7 +18,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task AddChainAsync(PrmApeChainAdd args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -45,7 +45,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task RemoveChainAsync(PrmApeChainRemove args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -70,7 +70,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task ListChainAsync(PrmApeChainList args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
index c42b2ba..14a3e2d 100644
--- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
@@ -12,11 +12,11 @@ using FrostFS.Session;
namespace FrostFS.SDK.ClientV2;
-internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider
+internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider
{
- readonly SessionProvider sessions = new(context);
+ readonly SessionProvider sessions = new(envCtx);
- public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx)
+ public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx)
{
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
@@ -35,8 +35,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args)
{
var ctx = args.Context!;
- ctx.OwnerId ??= Context.Owner;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.OwnerId ??= EnvironmentContext.Owner;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -147,7 +147,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
Verifier.CheckResponse(response);
}
- private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx)
+ private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
{
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -172,7 +172,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
Removed
}
- private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx)
+ private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx)
{
var request = GetContainerRequest(id, null, ctx);
diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
index 91cca73..91645fb 100644
--- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
@@ -12,27 +12,33 @@ internal sealed class NetmapServiceProvider : ContextAccessor
{
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
- internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context)
+ internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context)
: base(context)
{
this.netmapServiceClient = netmapServiceClient;
}
- internal async Task GetNetworkSettingsAsync(Context ctx)
+ internal async Task GetNetworkSettingsAsync(CallContext ctx)
{
- if (Context.NetworkSettings != null)
- return Context.NetworkSettings;
+ if (EnvironmentContext.NetworkSettings != null)
+ return EnvironmentContext.NetworkSettings;
- var info = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
+ var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
var settings = new NetworkSettings();
- foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters)
+ var info = response.Body.NetworkInfo;
+
+ settings.Epoch = info.CurrentEpoch;
+ settings.MagicNumber = info.MagicNumber;
+ settings.MsPerBlock = info.MsPerBlock;
+
+ foreach (var param in info.NetworkConfig.Parameters)
{
SetNetworksParam(param, settings);
}
- Context.NetworkSettings = settings;
+ EnvironmentContext.NetworkSettings = settings;
return settings;
}
@@ -40,7 +46,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -53,6 +59,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor
request.AddMetaHeader(args.XHeaders);
request.Sign(ctx.Key);
+
+
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
@@ -60,9 +68,9 @@ internal sealed class NetmapServiceProvider : ContextAccessor
return response.Body.ToModel();
}
- internal async Task GetNetworkInfoAsync(Context ctx)
+ internal async Task GetNetworkInfoAsync(CallContext ctx)
{
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -83,7 +91,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
index dce9787..6ab3362 100644
--- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
@@ -20,14 +20,14 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
private readonly SessionProvider sessions;
private ObjectService.ObjectServiceClient client;
- internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env)
+ internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env)
: base(env)
{
- this.sessions = new(Context);
+ this.sessions = new(EnvironmentContext);
this.client = client;
}
- public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx)
+ public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx)
{
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
@@ -35,7 +35,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
internal async Task GetObjectHeadAsync(PrmObjectHeadGet args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -74,7 +74,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -108,7 +108,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
internal async Task DeleteObjectAsync(PrmObjectDelete args)
{
var ctx = args.Context!;
- ctx.Key ??= Context.Key?.ECDsaKey;
+ ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
if (ctx.Key == null)
throw new InvalidObjectException(nameof(ctx.Key));
@@ -238,7 +238,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var ctx = args.Context!;
var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false);
- var token = new FrostFsSessionToken(tokenRaw.Serialize());
+ var token = new FrostFsSessionToken(tokenRaw.Serialize(), tokenRaw.Body.Id.ToUuid());
args.SessionToken = token;
@@ -254,7 +254,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
if (args.MaxObjectSizeCache == 0)
{
- var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
+ var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
.ConfigureAwait(false);
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
@@ -352,7 +352,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
}
else
{
- chunkBuffer = Context.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
+ chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
isRentBuffer = true;
}
@@ -404,7 +404,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
}
}
- private async Task GetUploadStream(PrmObjectPut args, Context ctx)
+ private async Task GetUploadStream(PrmObjectPut args, CallContext ctx)
{
var header = args.Header!;
@@ -449,7 +449,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
return await PutObjectInit(initRequest, ctx).ConfigureAwait(false);
}
- private async Task GetObject(GetRequest request, Context ctx)
+ private async Task GetObject(GetRequest request, CallContext ctx)
{
var reader = GetObjectInit(request, ctx);
@@ -461,7 +461,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
return modelObject;
}
- private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx)
+ private ObjectReader GetObjectInit(GetRequest initRequest, CallContext ctx)
{
if (initRequest is null)
throw new ArgumentNullException(nameof(initRequest));
@@ -471,7 +471,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
return new ObjectReader(call);
}
- private async Task PutObjectInit(PutRequest initRequest, Context ctx)
+ private async Task PutObjectInit(PutRequest initRequest, CallContext ctx)
{
if (initRequest is null)
{
@@ -485,7 +485,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
return new ObjectStreamer(call);
}
- private async IAsyncEnumerable SearchObjects(SearchRequest request, Context ctx)
+ private async IAsyncEnumerable SearchObjects(SearchRequest request, CallContext ctx)
{
using var stream = GetSearchReader(request, ctx);
@@ -503,7 +503,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
}
}
- private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx)
+ private SearchReader GetSearchReader(SearchRequest initRequest, CallContext ctx)
{
if (initRequest is null)
{
diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
index de22165..1dfe53f 100644
--- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
@@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
- internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context)
+ internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context)
: base(context)
{
_sessionServiceClient = sessionServiceClient;
@@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
var ctx = args.Context!;
- ctx.OwnerId ??= Context.Owner;
+ ctx.OwnerId ??= EnvironmentContext.Owner;
var request = new CreateRequest
{
@@ -37,7 +37,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
return await CreateSession(request, args.Context!).ConfigureAwait(false);
}
- internal async Task CreateSession(CreateRequest request, Context ctx)
+ internal async Task CreateSession(CreateRequest request, CallContext ctx)
{
var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken);
diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
index 05797de..3fb7674 100644
--- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-internal class ContextAccessor(ClientEnvironment context)
+internal class ContextAccessor(EnvironmentContext context)
{
- protected ClientEnvironment Context { get; set; } = context;
+ protected EnvironmentContext EnvironmentContext { get; set; } = context;
}
diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
index 8d0d1f5..51fe337 100644
--- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
@@ -4,16 +4,17 @@ namespace FrostFS.SDK.ClientV2;
internal interface ISessionProvider
{
- ValueTask GetOrCreateSession(ISessionToken args, Context ctx);
+ ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx);
}
-internal sealed class SessionProvider(ClientEnvironment env)
+internal sealed class SessionProvider(EnvironmentContext envCtx)
{
- public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx)
+ // TODO: implement cache for session in the next iteration
+ public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx)
{
if (args.SessionToken is null)
{
- return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
+ return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
.ConfigureAwait(false);
}
diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs
similarity index 87%
rename from src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs
rename to src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs
index 11023e7..d0596c9 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs
@@ -6,7 +6,7 @@ using Grpc.Net.Client;
namespace FrostFS.SDK.ClientV2;
-public class ClientEnvironment(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
+public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
{
private ArrayPool? _arrayPool;
diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs
index d06ce03..524f358 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs
@@ -4,6 +4,10 @@ namespace FrostFS.SDK.ClientV2;
public class NetworkSettings
{
+ public ulong Epoch { get; internal set; }
+ public ulong MagicNumber { get; internal set; }
+ public long MsPerBlock { get; internal set; }
+
public ulong AuditFee { get; internal set; }
public ulong BasicIncomeRate { get; internal set; }
public ulong ContainerFee { get; internal set; }
diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
index 0303626..f4d659c 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
@@ -11,7 +11,7 @@ namespace FrostFS.SDK.ClientV2;
internal static class ObjectTools
{
- internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx)
+ internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
{
var grpcHeader = CreateHeader(header, [], ctx);
@@ -21,7 +21,7 @@ internal static class ObjectTools
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
}
- internal static Object.Object CreateObject(FrostFsObject @object, Context ctx)
+ internal static Object.Object CreateObject(FrostFsObject @object, CallContext ctx)
{
@object.Header.OwnerId ??= ctx.OwnerId;
@object.Header.Version ??= ctx.Version;
@@ -53,7 +53,7 @@ internal static class ObjectTools
return obj;
}
- internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, Context ctx)
+ internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, CallContext ctx)
{
if (split == null)
return;
@@ -85,7 +85,7 @@ internal static class ObjectTools
grpcHeader.Split.Previous = split.Previous?.ToMessage();
}
- internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, Context ctx)
+ internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, CallContext ctx)
{
header.OwnerId ??= ctx.OwnerId;
header.Version ??= ctx.Version;
diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs
index 13d87b0..f1006a4 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs
@@ -76,6 +76,11 @@ public static class RequestSigner
public static byte[] SignData(this ECDsa key, byte[] data)
{
+ if (key is null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
var hash = new byte[65];
hash[0] = 0x04;
diff --git a/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs
new file mode 100644
index 0000000..af77424
--- /dev/null
+++ b/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs
@@ -0,0 +1,62 @@
+using FrostFS.SDK.ProtosV2.Interfaces;
+using FrostFS.Session;
+
+using Google.Protobuf;
+
+namespace FrostFS.Accounting;
+
+public partial class BalanceRequest : IRequest
+{
+ IMetaHeader IVerifiableMessage.GetMetaHeader()
+ {
+ return MetaHeader;
+ }
+
+ IVerificationHeader IVerifiableMessage.GetVerificationHeader()
+ {
+ return VerifyHeader;
+ }
+
+ void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader)
+ {
+ MetaHeader = (RequestMetaHeader)metaHeader;
+ }
+
+ void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader)
+ {
+ VerifyHeader = (RequestVerificationHeader)verificationHeader;
+ }
+
+ public IMessage GetBody()
+ {
+ return Body;
+ }
+}
+
+public partial class BalanceResponse : IResponse
+{
+ IMetaHeader IVerifiableMessage.GetMetaHeader()
+ {
+ return MetaHeader;
+ }
+
+ IVerificationHeader IVerifiableMessage.GetVerificationHeader()
+ {
+ return VerifyHeader;
+ }
+
+ void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader)
+ {
+ MetaHeader = (ResponseMetaHeader)metaHeader;
+ }
+
+ void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader)
+ {
+ VerifyHeader = (ResponseVerificationHeader)verificationHeader;
+ }
+
+ public IMessage GetBody()
+ {
+ return Body;
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs
index e58e18c..8f4e868 100644
--- a/src/FrostFS.SDK.Tests/ContainerTest.cs
+++ b/src/FrostFS.SDK.Tests/ContainerTest.cs
@@ -1,49 +1,14 @@
+using System.Diagnostics.CodeAnalysis;
+
using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
-using Microsoft.Extensions.Options;
-
namespace FrostFS.SDK.Tests;
-public abstract class ContainerTestsBase
-{
- protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
-
- protected IOptions Settings { get; set; }
- protected ContainerMocker Mocker { get; set; }
-
- protected ContainerTestsBase()
- {
- Settings = Options.Create(new SingleOwnerClientSettings
- {
- Key = key,
- Host = "http://localhost:8080"
- });
-
- Mocker = new ContainerMocker(this.key)
- {
- PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
- Version = new FrostFsVersion(2, 13),
- ContainerGuid = Guid.NewGuid()
- };
- }
-
- protected IFrostFSClient GetClient()
- {
- return ClientV2.FrostFSClient.GetTestInstance(
- Settings,
- null,
- new NetworkMocker(this.key).GetMock().Object,
- new SessionMocker(this.key).GetMock().Object,
- Mocker.GetMock().Object,
- new ObjectMocker(this.key).GetMock().Object);
- }
-}
-
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
public class ContainerTest : ContainerTestsBase
{
[Fact]
diff --git a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs
new file mode 100644
index 0000000..f9bfc9a
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs
@@ -0,0 +1,40 @@
+using FrostFS.SDK.ClientV2.Interfaces;
+
+using Microsoft.Extensions.Options;
+
+namespace FrostFS.SDK.Tests;
+
+public abstract class ContainerTestsBase
+{
+ internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
+
+ protected IOptions Settings { get; set; }
+ protected ContainerMocker Mocker { get; set; }
+
+ protected ContainerTestsBase()
+ {
+ Settings = Options.Create(new SingleOwnerClientSettings
+ {
+ Key = key,
+ Host = "http://localhost:8080"
+ });
+
+ Mocker = new ContainerMocker(this.key)
+ {
+ PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
+ Version = new FrostFsVersion(2, 13),
+ ContainerGuid = Guid.NewGuid()
+ };
+ }
+
+ protected IFrostFSClient GetClient()
+ {
+ return ClientV2.FrostFSClient.GetTestInstance(
+ Settings,
+ null,
+ new NetworkMocker(this.key).GetMock().Object,
+ new SessionMocker(this.key).GetMock().Object,
+ Mocker.GetMock().Object,
+ new ObjectMocker(this.key).GetMock().Object);
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/GlobalSuppressions.cs b/src/FrostFS.SDK.Tests/GlobalSuppressions.cs
new file mode 100644
index 0000000..d1eb84b
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/GlobalSuppressions.cs
@@ -0,0 +1,6 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+
diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs
index 958a73e..9f5d824 100644
--- a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs
+++ b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs
@@ -12,7 +12,9 @@ public class MetricsInterceptor() : Interceptor
ClientInterceptorContext context,
AsyncUnaryCallContinuation continuation)
{
- var call = continuation(request, context);
+ ArgumentNullException.ThrowIfNull(continuation);
+
+ using var call = continuation(request, context);
return new AsyncUnaryCall(
HandleUnaryResponse(call),
@@ -27,7 +29,7 @@ public class MetricsInterceptor() : Interceptor
var watch = new Stopwatch();
watch.Start();
- var response = await call.ResponseAsync;
+ var response = await call.ResponseAsync.ConfigureAwait(false);
watch.Stop();
diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs
index 22172f2..63e3d64 100644
--- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs
@@ -26,8 +26,11 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader)
OwnerId = objectHeader.OwnerId!.ToMessage()
};
- foreach (var attr in objectHeader.Attributes)
- header.Attributes.Add(attr.ToMessage());
+ if (objectHeader.Attributes != null)
+ {
+ foreach (var attr in objectHeader.Attributes)
+ header.Attributes.Add(attr.ToMessage());
+ }
var response = new GetResponse
{
diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs
index 9836d37..f386468 100644
--- a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs
@@ -1,3 +1,5 @@
+using System.Collections.ObjectModel;
+
using FrostFS.SDK.ProtosV2.Interfaces;
using Grpc.Core;
@@ -6,13 +8,15 @@ namespace FrostFS.SDK.Tests;
public class ClientStreamWriter : IClientStreamWriter
{
- public List Messages { get; set; } = [];
+ private WriteOptions? _options;
+
+ public Collection Messages { get; } = [];
public bool CompletedTask { get; private set; }
public WriteOptions? WriteOptions
{
- get => throw new NotImplementedException();
- set => throw new NotImplementedException();
+ get => _options;
+ set => _options = value;
}
public Task CompleteAsync()
diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs
index ba34cde..e727cfb 100644
--- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs
@@ -25,7 +25,10 @@ public abstract class ServiceBase(string key)
public static FrostFsVersion DefaultVersion { get; } = new(2, 13);
public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1));
- public Metadata Metadata { get; protected set; }
+#pragma warning disable CA2227 // this is specific object, should be treated as is
+ public Metadata? Metadata { get; set; }
+#pragma warning restore CA2227
+
public DateTime? DateTime { get; protected set; }
public CancellationToken CancellationToken { get; protected set; }
@@ -35,6 +38,8 @@ public abstract class ServiceBase(string key)
protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response)
{
+ ArgumentNullException.ThrowIfNull(response);
+
var verifyHeader = new ResponseVerificationHeader
{
MetaSignature = new Refs.Signature
diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs
index d77322a..aa4d048 100644
--- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs
@@ -1,3 +1,5 @@
+using System.Collections.ObjectModel;
+
using FrostFS.Container;
using FrostFS.Refs;
using FrostFS.SDK.ClientV2;
@@ -41,7 +43,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse);
var metadata = new Metadata();
- var putContainerResponse = new AsyncUnaryCall(
+ using var putContainerResponse = new AsyncUnaryCall(
Task.FromResult(putResponse),
Task.FromResult(metadata),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
@@ -180,7 +182,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
public bool ReturnContainerRemoved { get; set; }
- public List ContainerIds { get; set; } = [];
+ public Collection ContainerIds { get; } = [];
- public List> Requests { get; set; } = [];
+ public Collection> Requests { get; } = [];
}
diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs
index 72b3aab..003d7ac 100644
--- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs
@@ -25,18 +25,17 @@ public class NetworkMocker(string key) : ServiceBase(key)
"MaintenanceModeAllowed"
];
- public Dictionary? Parameters { get; set; }
+ public Dictionary Parameters { get; } = [];
- public LocalNodeInfoResponse NodeInfoResponse { get; set; }
+ public LocalNodeInfoResponse? NodeInfoResponse { get; set; }
- public LocalNodeInfoRequest LocalNodeInfoRequest { get; set; }
+ public LocalNodeInfoRequest? LocalNodeInfoRequest { get; set; }
- public NetworkInfoRequest NetworkInfoRequest { get; set; }
+ public NetworkInfoRequest? NetworkInfoRequest { get; set; }
- public NetmapSnapshotResponse NetmapSnapshotResponse { get; set; }
-
- public NetmapSnapshotRequest NetmapSnapshotRequest { get; set; }
+ public NetmapSnapshotResponse? NetmapSnapshotResponse { get; set; }
+ public NetmapSnapshotRequest? NetmapSnapshotRequest { get; set; }
public Mock GetMock()
{
diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs
index 1dc9152..d0b900d 100644
--- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs
@@ -1,3 +1,4 @@
+using System.Collections.ObjectModel;
using System.Security.Cryptography;
using FrostFS.Object;
@@ -92,7 +93,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
}
- if (ResultObjectIds != null)
+ if (ResultObjectIds != null && ResultObjectIds.Count > 0)
{
PutResponse putResponse = new()
{
@@ -197,14 +198,14 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
public Header? HeadResponse { get; set; }
- public List? ResultObjectIds { get; set; }
+ public Collection? ResultObjectIds { get; } = [];
public ClientStreamWriter? ClientStreamWriter { get; private set; } = new();
- public List PutSingleRequests { get; private set; } = [];
+ public Collection PutSingleRequests { get; private set; } = [];
- public List DeleteRequests { get; private set; } = [];
+ public Collection DeleteRequests { get; private set; } = [];
- public List HeadRequests { get; private set; } = [];
+ public Collection HeadRequests { get; private set; } = [];
}
diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs
index 359f10b..88ed60d 100644
--- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs
+++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs
@@ -8,13 +8,14 @@ using Moq;
namespace FrostFS.SDK.Tests;
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
public class SessionMocker(string key) : ServiceBase(key)
{
public byte[]? SessionId { get; set; }
public byte[]? SessionKey { get; set; }
- public CreateRequest CreateSessionRequest { get; private set; }
+ public CreateRequest? CreateSessionRequest { get; private set; }
public Mock GetMock()
{
@@ -24,7 +25,7 @@ public class SessionMocker(string key) : ServiceBase(key)
if (SessionId == null)
{
- SessionId = new byte[32];
+ SessionId = new byte[16];
rand.NextBytes(SessionId);
}
diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs
index 0079d82..f09cafb 100644
--- a/src/FrostFS.SDK.Tests/NetworkTest.cs
+++ b/src/FrostFS.SDK.Tests/NetworkTest.cs
@@ -1,55 +1,13 @@
-using System.Security.Cryptography;
+using System.Diagnostics.CodeAnalysis;
using FrostFS.Netmap;
using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.ClientV2.Interfaces;
-using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.Cryptography;
using Google.Protobuf;
-using Microsoft.Extensions.Options;
-
namespace FrostFS.SDK.Tests;
-public abstract class NetworkTestsBase
-{
- protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
-
- protected IOptions Settings { get; set; }
-
- protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13);
-
- protected ECDsa ECDsaKey { get; set; }
- protected FrostFsOwner OwnerId { get; set; }
- protected NetworkMocker Mocker { get; set; }
-
- protected NetworkTestsBase()
- {
- Settings = Options.Create(new SingleOwnerClientSettings
- {
- Key = key,
- Host = "http://localhost:8080"
- });
-
- ECDsaKey = key.LoadWif();
- OwnerId = FrostFsOwner.FromKey(ECDsaKey);
-
- Mocker = new NetworkMocker(this.key);
- }
-
- protected IFrostFSClient GetClient()
- {
- return ClientV2.FrostFSClient.GetTestInstance(
- Settings,
- null,
- Mocker.GetMock().Object,
- new SessionMocker(this.key).GetMock().Object,
- new ContainerMocker(this.key).GetMock().Object,
- new ObjectMocker(this.key).GetMock().Object);
- }
-}
-
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
public class NetworkTest : NetworkTestsBase
{
[Theory]
@@ -57,27 +15,24 @@ public class NetworkTest : NetworkTestsBase
[InlineData(true)]
public async void NetworkSettingsTest(bool useContext)
{
- Mocker.Parameters = new Dictionary
- {
- { "AuditFee", [1] },
- { "BasicIncomeRate", [2] },
- { "ContainerFee", [3] },
- { "ContainerAliasFee", [4] },
- { "EpochDuration", [5] },
- { "InnerRingCandidateFee", [6] },
- { "MaxECDataCount", [7] },
- { "MaxECParityCount", [8] },
- { "MaxObjectSize", [9] },
- { "WithdrawFee", [10] },
- { "HomomorphicHashingDisabled", [1] },
- { "MaintenanceModeAllowed", [1] },
- };
+ Mocker.Parameters.Add("AuditFee", [1]);
+ Mocker.Parameters.Add("BasicIncomeRate", [2]);
+ Mocker.Parameters.Add("ContainerFee", [3]);
+ Mocker.Parameters.Add("ContainerAliasFee", [4]);
+ Mocker.Parameters.Add("EpochDuration", [5]);
+ Mocker.Parameters.Add("InnerRingCandidateFee", [6]);
+ Mocker.Parameters.Add("MaxECDataCount", [7]);
+ Mocker.Parameters.Add("MaxECParityCount", [8]);
+ Mocker.Parameters.Add("MaxObjectSize", [9]);
+ Mocker.Parameters.Add("WithdrawFee", [10]);
+ Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
+ Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
var param = new PrmNetworkSettings();
if (useContext)
{
- param.Context = new Context
+ param.Context = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -119,6 +74,7 @@ public class NetworkTest : NetworkTestsBase
}
else
{
+ Assert.NotNull(Mocker.NetworkInfoRequest);
Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
@@ -127,6 +83,7 @@ public class NetworkTest : NetworkTestsBase
[Theory]
[InlineData(false)]
[InlineData(true)]
+
public async void NetmapSnapshotTest(bool useContext)
{
var body = new NetmapSnapshotResponse.Types.Body
@@ -164,7 +121,7 @@ public class NetworkTest : NetworkTestsBase
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new Context
+ param.Context = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -210,6 +167,7 @@ public class NetworkTest : NetworkTestsBase
if (useContext)
{
+ Assert.NotNull(Mocker.NetmapSnapshotRequest);
Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key);
Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value);
@@ -222,6 +180,7 @@ public class NetworkTest : NetworkTestsBase
}
else
{
+ Assert.NotNull(Mocker.NetmapSnapshotRequest);
Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
@@ -254,7 +213,7 @@ public class NetworkTest : NetworkTestsBase
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new Context
+ param.Context = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -282,6 +241,7 @@ public class NetworkTest : NetworkTestsBase
Assert.Equal("value1", result.Attributes["key1"]);
Assert.Equal("value2", result.Attributes["key2"]);
+ Assert.NotNull(Mocker.LocalNodeInfoRequest);
if (useContext)
{
Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders);
diff --git a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs
new file mode 100644
index 0000000..071b425
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs
@@ -0,0 +1,48 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
+
+using FrostFS.SDK.ClientV2.Interfaces;
+using FrostFS.SDK.Cryptography;
+
+using Microsoft.Extensions.Options;
+
+namespace FrostFS.SDK.Tests;
+
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
+public abstract class NetworkTestsBase
+{
+ internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
+
+ protected IOptions Settings { get; set; }
+
+ protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13);
+
+ protected ECDsa ECDsaKey { get; set; }
+ protected FrostFsOwner OwnerId { get; set; }
+ protected NetworkMocker Mocker { get; set; }
+
+ protected NetworkTestsBase()
+ {
+ Settings = Options.Create(new SingleOwnerClientSettings
+ {
+ Key = key,
+ Host = "http://localhost:8080"
+ });
+
+ ECDsaKey = key.LoadWif();
+ OwnerId = FrostFsOwner.FromKey(ECDsaKey);
+
+ Mocker = new NetworkMocker(this.key);
+ }
+
+ protected IFrostFSClient GetClient()
+ {
+ return ClientV2.FrostFSClient.GetTestInstance(
+ Settings,
+ null,
+ Mocker.GetMock().Object,
+ new SessionMocker(this.key).GetMock().Object,
+ new ContainerMocker(this.key).GetMock().Object,
+ new ObjectMocker(this.key).GetMock().Object);
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs
index af58346..07fa8e9 100644
--- a/src/FrostFS.SDK.Tests/ObjectTest.cs
+++ b/src/FrostFS.SDK.Tests/ObjectTest.cs
@@ -1,71 +1,19 @@
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using FrostFS.Refs;
using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
-using FrostFS.SDK.ClientV2;
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
-using Microsoft.Extensions.Options;
-
namespace FrostFS.SDK.Tests;
-public abstract class ObjectTestsBase
-{
- protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
-
- protected IOptions Settings { get; set; }
- protected FrostFsContainerId ContainerId { get; set; }
-
- protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key);
- protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key);
- protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key);
- protected ObjectMocker Mocker { get; set; }
-
- protected ObjectTestsBase()
- {
- var ecdsaKey = key.LoadWif();
-
- Settings = Options.Create(new SingleOwnerClientSettings
- {
- Key = key,
- Host = "http://localhost:8080"
- });
-
- Mocker = new ObjectMocker(key)
- {
- PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
- Version = new FrostFsVersion(2, 13),
- ContainerGuid = Guid.NewGuid()
- };
-
- ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
-
- Mocker.ObjectHeader = new(
- ContainerId,
- FrostFsObjectType.Regular,
- [new FrostFsAttributePair("k", "v")],
- null,
- FrostFsOwner.FromKey(ecdsaKey),
- new FrostFsVersion(2, 13));
- }
-
- protected IFrostFSClient GetClient()
- {
- return FrostFSClient.GetTestInstance(
- Settings,
- null,
- NetworkMocker.GetMock().Object,
- SessionMocker.GetMock().Object,
- ContainerMocker.GetMock().Object,
- Mocker.GetMock().Object);
- }
-}
-
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
+[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
public class ObjectTest : ObjectTestsBase
{
[Fact]
@@ -75,7 +23,7 @@ public class ObjectTest : ObjectTestsBase
var ecdsaKey = key.LoadWif();
- var ctx = new Context
+ var ctx = new CallContext
{
Key = ecdsaKey,
OwnerId = FrostFsOwner.FromKey(ecdsaKey),
@@ -88,18 +36,21 @@ public class ObjectTest : ObjectTestsBase
Assert.NotNull(result);
- Assert.Equal(Mocker.ObjectHeader!.ContainerId.GetValue(), result.Header.ContainerId.GetValue());
- Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value);
+ Assert.NotNull(Mocker.ObjectHeader);
+
+ Assert.Equal(Mocker.ObjectHeader.ContainerId.GetValue(), result.Header.ContainerId.GetValue());
+ Assert.Equal(Mocker.ObjectHeader.OwnerId!.Value, result.Header.OwnerId!.Value);
Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength);
+ Assert.NotNull(result.Header.Attributes);
Assert.Single(result.Header.Attributes);
- Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key);
- Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value, result.Header.Attributes[0].Value);
+ Assert.Equal(Mocker.ObjectHeader.Attributes![0].Key, result.Header.Attributes[0].Key);
+ Assert.Equal(Mocker.ObjectHeader.Attributes![0].Value, result.Header.Attributes[0].Value);
}
[Fact]
public async void PutObjectTest()
{
- Mocker.ResultObjectIds = new([SHA256.HashData([])]);
+ Mocker.ResultObjectIds.Add(SHA256.HashData([]));
Random rnd = new();
var bytes = new byte[1024];
@@ -134,7 +85,7 @@ public class ObjectTest : ObjectTestsBase
[Fact]
public async void ClientCutTest()
{
- NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } };
+ NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]);
var blockSize = 2560;
byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg");
@@ -150,17 +101,19 @@ public class ObjectTest : ObjectTestsBase
Random rnd = new();
- List objIds = new([new byte[32], new byte[32], new byte[32]]);
+ Collection objIds = new([new byte[32], new byte[32], new byte[32]]);
rnd.NextBytes(objIds.ElementAt(0));
rnd.NextBytes(objIds.ElementAt(1));
rnd.NextBytes(objIds.ElementAt(2));
- Mocker.ResultObjectIds = objIds;
+ foreach (var objId in objIds)
+ Mocker.ResultObjectIds.Add(objId);
var result = await GetClient().PutObjectAsync(param);
var singleObjects = Mocker.PutSingleRequests.ToArray();
+ Assert.NotNull(Mocker.ClientStreamWriter?.Messages);
var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray();
Assert.Single(singleObjects);
@@ -262,6 +215,7 @@ public class ObjectTest : ObjectTestsBase
Assert.Equal(FrostFsObjectType.Regular, response.ObjectType);
+ Assert.NotNull(response.Attributes);
Assert.Single(response.Attributes);
Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key);
diff --git a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs
new file mode 100644
index 0000000..543bda6
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs
@@ -0,0 +1,59 @@
+using FrostFS.SDK.ClientV2;
+using FrostFS.SDK.ClientV2.Interfaces;
+using FrostFS.SDK.Cryptography;
+
+using Microsoft.Extensions.Options;
+
+namespace FrostFS.SDK.Tests;
+
+public abstract class ObjectTestsBase
+{
+ protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
+
+ protected IOptions Settings { get; set; }
+ protected FrostFsContainerId ContainerId { get; set; }
+
+ protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key);
+ protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key);
+ protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key);
+ protected ObjectMocker Mocker { get; set; }
+
+ protected ObjectTestsBase()
+ {
+ var ecdsaKey = key.LoadWif();
+
+ Settings = Options.Create(new SingleOwnerClientSettings
+ {
+ Key = key,
+ Host = "http://localhost:8080"
+ });
+
+ Mocker = new ObjectMocker(key)
+ {
+ PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
+ Version = new FrostFsVersion(2, 13),
+ ContainerGuid = Guid.NewGuid()
+ };
+
+ ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
+
+ Mocker.ObjectHeader = new(
+ ContainerId,
+ FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("k", "v")],
+ null,
+ FrostFsOwner.FromKey(ecdsaKey),
+ new FrostFsVersion(2, 13));
+ }
+
+ protected IFrostFSClient GetClient()
+ {
+ return FrostFSClient.GetTestInstance(
+ Settings,
+ null,
+ NetworkMocker.GetMock().Object,
+ SessionMocker.GetMock().Object,
+ ContainerMocker.GetMock().Object,
+ Mocker.GetMock().Object);
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs
new file mode 100644
index 0000000..87bf106
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs
@@ -0,0 +1,603 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
+
+using FrostFS.SDK.ClientV2;
+using FrostFS.SDK.Cryptography;
+
+using Microsoft.Extensions.Options;
+
+using static FrostFS.Session.SessionToken.Types.Body;
+
+namespace FrostFS.SDK.SmokeTests;
+
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
+[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
+public class PoolSmokeTests : SmokeTestsBase
+{
+ private static readonly PrmWait lightWait = new(100, 1);
+
+ private InitParameters GetDefaultParams()
+ {
+ return new InitParameters
+ {
+ Key = keyString.LoadWif(),
+
+ NodeParams = [new(1, this.url, 100.0f)],
+ DialOptions = [new()
+ {
+ Authority = "",
+ Block = false,
+ DisableHealthCheck = false,
+ DisableRetry = false,
+ ReturnLastError = true,
+ Timeout = 30_000_000
+ }
+ ],
+ ClientBuilder = null,
+ GracefulCloseOnSwitchTimeout = 30_000_000,
+ Logger = null
+ };
+ }
+
+ [Fact]
+ public async void NetworkMapTest()
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext());
+
+ Assert.Null(error);
+
+ var result = await pool.GetNetmapSnapshotAsync(default);
+
+ Assert.True(result.Epoch > 0);
+ Assert.Single(result.NodeInfoCollection);
+
+ var item = result.NodeInfoCollection[0];
+ Assert.Equal(2, item.Version.Major);
+ Assert.Equal(13, item.Version.Minor);
+ Assert.Equal(NodeState.Online, item.State);
+ Assert.True(item.PublicKey.Length > 0);
+ Assert.Single(item.Addresses);
+ Assert.Equal(9, item.Attributes.Count);
+ }
+
+ [Fact]
+ public async void NodeInfoTest()
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext());
+
+ Assert.Null(error);
+
+ var result = await pool.GetNodeInfoAsync();
+
+ 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);
+ }
+
+ [Fact]
+ public async void NodeInfoStatisticsTest()
+ {
+ var options = GetDefaultParams();
+
+ 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();
+
+ Assert.False(string.IsNullOrEmpty(callbackText));
+ Assert.Contains(" took ", callbackText, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public async void GetSessionTest()
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ var prm = new PrmSessionCreate(100);
+
+ var token = await pool.CreateSessionAsync(prm).ConfigureAwait(true);
+
+ var session = new Session.SessionToken().Deserialize(token.Token);
+
+ var ownerHash = Base58.Decode(OwnerId!.Value);
+
+ Assert.NotNull(session);
+ Assert.Null(session.Body.Container);
+ Assert.Null(session.Body.Object);
+ Assert.Equal(16, session.Body.Id.Length);
+ Assert.Equal(100ul, session.Body.Lifetime.Exp);
+ Assert.Equal(ownerHash, session.Body.OwnerId.Value);
+ Assert.Equal(33, session.Body.SessionKey.Length);
+ Assert.Equal(ContextOneofCase.None, session.Body.ContextCase);
+ }
+
+ [Fact]
+ public async void CreateObjectWithSessionToken()
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ await Cleanup(pool);
+
+ var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
+
+ var createContainerParam = new PrmContainerCreate(
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))));
+
+ createContainerParam.XHeaders.Add("key1", "value1");
+
+ var containerId = await pool.CreateContainerAsync(createContainerParam);
+
+ var bytes = GetRandomBytes(1024);
+
+ var param = new PrmObjectPut
+ {
+ Header = new FrostFsObjectHeader(
+ containerId: containerId,
+ type: FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("fileName", "test")]),
+ Payload = new MemoryStream(bytes),
+ ClientCut = false,
+ SessionToken = token
+ };
+
+ var objectId = await pool.PutObjectAsync(param).ConfigureAwait(true);
+
+ var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId));
+
+ var downloadedBytes = new byte[@object.Header.PayloadLength];
+ MemoryStream ms = new(downloadedBytes);
+
+ ReadOnlyMemory? chunk = null;
+ while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
+ {
+ ms.Write(chunk.Value.Span);
+ }
+
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
+
+ await Cleanup(pool);
+ }
+
+ [Fact]
+ public async void FilterTest()
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ await Cleanup(pool);
+
+ var createContainerParam = new PrmContainerCreate(
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
+ {
+ WaitParams = lightWait
+ };
+
+ var containerId = await pool.CreateContainerAsync(createContainerParam);
+
+ var bytes = new byte[] { 1, 2, 3 };
+
+ var ParentHeader = new FrostFsObjectHeader(
+ containerId: containerId,
+ type: FrostFsObjectType.Regular)
+ {
+ PayloadLength = 3
+ };
+
+ var param = new PrmObjectPut
+ {
+ Header = new FrostFsObjectHeader(
+ containerId: containerId,
+ type: FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("fileName", "test")],
+ new FrostFsSplit()),
+ Payload = new MemoryStream(bytes),
+ ClientCut = false
+ };
+
+ var objectId = await pool.PutObjectAsync(param);
+
+ var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
+
+ var ecdsaKey = this.keyString.LoadWif();
+
+ var networkInfo = await pool.GetNetmapSnapshotAsync();
+
+ await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId));
+
+ await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey)));
+
+ await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId));
+
+ await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"));
+
+ await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId));
+
+ await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version));
+
+ await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch));
+
+ await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3));
+
+ var checkSum = CheckSum.CreateCheckSum(bytes);
+
+ await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum));
+
+ await CheckFilter(pool, containerId, new FilterByPhysicallyStored());
+ }
+
+ private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter)
+ {
+ var resultObjectsCount = 0;
+
+ PrmObjectSearch searchParam = new(containerId) { Filters = [filter] };
+
+ await foreach (var objId in pool.SearchObjectsAsync(searchParam))
+ {
+ resultObjectsCount++;
+ var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId));
+ }
+
+ Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work");
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
+ [InlineData(6 * 1024 * 1024 + 100)]
+ public async void SimpleScenarioTest(int objectSize)
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ await Cleanup(pool);
+
+ bool callbackInvoked = false;
+ var ctx = new CallContext
+ {
+ // Timeout = TimeSpan.FromSeconds(20),
+ Callback = new((CallStatistics cs) =>
+ {
+ callbackInvoked = true;
+ Assert.True(cs.ElapsedMicroSeconds > 0);
+ })
+ };
+
+ var createContainerParam = new PrmContainerCreate(
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
+ {
+ Context = ctx
+ };
+
+ var createdContainer = await pool.CreateContainerAsync(createContainerParam);
+
+ var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
+ Assert.NotNull(container);
+ Assert.True(callbackInvoked);
+
+ var bytes = GetRandomBytes(objectSize);
+
+ var param = new PrmObjectPut
+ {
+ Header = new FrostFsObjectHeader(
+ containerId: createdContainer,
+ type: FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("fileName", "test")]),
+ Payload = new MemoryStream(bytes),
+ ClientCut = false,
+ Context = new CallContext
+ {
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ }
+ };
+
+ var objectId = await pool.PutObjectAsync(param);
+
+ var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
+
+ bool hasObject = false;
+ await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] }))
+ {
+ hasObject = true;
+
+ var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId));
+ Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
+ Assert.Single(objHeader.Attributes!);
+ Assert.Equal("fileName", objHeader.Attributes!.First().Key);
+ Assert.Equal("test", objHeader.Attributes!.First().Value);
+ }
+
+ Assert.True(hasObject);
+
+ var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId));
+
+ var downloadedBytes = new byte[@object.Header.PayloadLength];
+ MemoryStream ms = new(downloadedBytes);
+
+ ReadOnlyMemory? chunk = null;
+ while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
+ {
+ ms.Write(chunk.Value.Span);
+ }
+
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
+
+ await Cleanup(pool);
+
+ await foreach (var _ in pool.ListContainersAsync())
+ {
+ Assert.Fail("Containers exist");
+ }
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
+ [InlineData(6 * 1024 * 1024 + 100)]
+ public async void SimpleScenarioWithSessionTest(int objectSize)
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
+
+ await Cleanup(pool);
+
+ var ctx = new CallContext
+ {
+ Timeout = TimeSpan.FromSeconds(20),
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ };
+
+ var createContainerParam = new PrmContainerCreate(
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
+ {
+ Context = ctx
+ };
+
+ var container = await pool.CreateContainerAsync(createContainerParam);
+
+ var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
+ Assert.NotNull(containerInfo);
+
+ var bytes = GetRandomBytes(objectSize);
+
+ var param = new PrmObjectPut
+ {
+ Header = new FrostFsObjectHeader(
+ containerId: container,
+ type: FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("fileName", "test")]),
+ Payload = new MemoryStream(bytes),
+ ClientCut = false,
+ Context = new CallContext
+ {
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ },
+ SessionToken = token
+ };
+
+ var objectId = await pool.PutObjectAsync(param);
+
+ var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
+
+ bool hasObject = false;
+ await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token }))
+ {
+ hasObject = true;
+
+ var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token });
+ Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
+ Assert.Single(objHeader.Attributes);
+ Assert.Equal("fileName", objHeader.Attributes.First().Key);
+ Assert.Equal("test", objHeader.Attributes.First().Value);
+ }
+
+ Assert.True(hasObject);
+
+ var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token });
+
+ var downloadedBytes = new byte[@object.Header.PayloadLength];
+ MemoryStream ms = new(downloadedBytes);
+
+ ReadOnlyMemory? chunk = null;
+ while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
+ {
+ ms.Write(chunk.Value.Span);
+ }
+
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
+
+ await Cleanup(pool);
+
+ await foreach (var _ in pool.ListContainersAsync())
+ {
+ Assert.Fail("Containers exist");
+ }
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB
+ [InlineData(64 * 1024 * 1024 - 1)]
+ [InlineData(64 * 1024 * 1024 + 1)]
+ [InlineData(2 * 64 * 1024 * 1024 + 256)]
+ [InlineData(200)]
+ public async void ClientCutScenarioTest(int objectSize)
+ {
+ var options = GetDefaultParams();
+
+ using var pool = new Pool(options);
+
+ var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ await Cleanup(pool);
+
+ var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
+ {
+ WaitParams = lightWait
+ };
+
+ var containerId = await pool.CreateContainerAsync(createContainerParam);
+
+ var ctx = new CallContext
+ {
+ Timeout = TimeSpan.FromSeconds(10),
+ Interceptors = new([new MetricsInterceptor()])
+ };
+
+ var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
+
+ Assert.NotNull(container);
+
+ byte[] bytes = GetRandomBytes(objectSize);
+
+ var param = new PrmObjectPut
+ {
+ Header = new FrostFsObjectHeader(
+ containerId: containerId,
+ type: FrostFsObjectType.Regular,
+ [new FrostFsAttributePair("fileName", "test")]),
+ Payload = new MemoryStream(bytes),
+ ClientCut = true
+ };
+
+ var objectId = await pool.PutObjectAsync(param);
+
+ var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
+
+ bool hasObject = false;
+ await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
+ {
+ hasObject = true;
+
+ var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
+ Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
+ Assert.Single(objHeader.Attributes);
+ Assert.Equal("fileName", objHeader.Attributes[0].Key);
+ Assert.Equal("test", objHeader.Attributes[0].Value);
+ }
+
+ Assert.True(hasObject);
+
+ var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId));
+
+ var downloadedBytes = new byte[@object.Header.PayloadLength];
+ MemoryStream ms = new(downloadedBytes);
+
+ ReadOnlyMemory? chunk = null;
+ while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
+ {
+ ms.Write(chunk.Value.Span);
+ }
+
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
+
+ await CheckFilter(pool, containerId, new FilterByRootObject());
+
+ await Cleanup(pool);
+
+ var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
+
+ IAsyncEnumerator? enumerator = null;
+ do
+ {
+ if (deadline <= DateTime.UtcNow)
+ {
+ Assert.Fail("Containers exist");
+ break;
+ }
+
+ enumerator = pool.ListContainersAsync().GetAsyncEnumerator();
+ await Task.Delay(500);
+ }
+ while (await enumerator!.MoveNextAsync());
+ }
+
+ private static byte[] GetRandomBytes(int size)
+ {
+ Random rnd = new();
+ var bytes = new byte[size];
+ rnd.NextBytes(bytes);
+ return bytes;
+ }
+
+ private static IOptions GetSingleOwnerOptions(string key, string url)
+ {
+ return Options.Create(new SingleOwnerClientSettings
+ {
+ Key = key,
+ Host = url
+ });
+ }
+
+ private static IOptions GetOptions(string url)
+ {
+ return Options.Create(new ClientSettings
+ {
+ Host = url
+ });
+ }
+
+ static async Task Cleanup(Pool pool)
+ {
+ await foreach (var cid in pool.ListContainersAsync())
+ {
+ await pool.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }).ConfigureAwait(true);
+ }
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs
index 2e17816..e343c82 100644
--- a/src/FrostFS.SDK.Tests/SessionTests.cs
+++ b/src/FrostFS.SDK.Tests/SessionTests.cs
@@ -1,56 +1,11 @@
-using System.Security.Cryptography;
+using System.Diagnostics.CodeAnalysis;
using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
-using FrostFS.SDK.ClientV2;
-using FrostFS.SDK.Cryptography;
-
-using Microsoft.Extensions.Options;
namespace FrostFS.SDK.Tests;
-public abstract class SessionTestsBase
-{
- protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
-
- protected IOptions Settings { get; set; }
-
-
- protected ECDsa ECDsaKey { get; set; }
- protected FrostFsOwner OwnerId { get; set; }
- protected SessionMocker Mocker { get; set; }
-
- protected SessionTestsBase()
- {
- Settings = Options.Create(new SingleOwnerClientSettings
- {
- Key = key,
- Host = "http://localhost:8080"
- });
-
- ECDsaKey = key.LoadWif();
- OwnerId = FrostFsOwner.FromKey(ECDsaKey);
-
- Mocker = new SessionMocker(this.key)
- {
- PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
- Version = new FrostFsVersion(2, 13)
- };
- }
-
- protected IFrostFSClient GetClient()
- {
- return ClientV2.FrostFSClient.GetTestInstance(
- Settings,
- null,
- new NetworkMocker(this.key).GetMock().Object,
- Mocker.GetMock().Object,
- new ContainerMocker(this.key).GetMock().Object,
- new ObjectMocker(this.key).GetMock().Object);
- }
-}
-
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
public class SessionTest : SessionTestsBase
{
[Theory]
@@ -64,7 +19,7 @@ public class SessionTest : SessionTestsBase
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new Context
+ param.Context = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -101,7 +56,6 @@ public class SessionTest : SessionTestsBase
Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader);
Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version);
-
Assert.Null(Mocker.Metadata);
if (useContext)
diff --git a/src/FrostFS.SDK.Tests/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/SessionTestsBase.cs
new file mode 100644
index 0000000..a8fbdaf
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/SessionTestsBase.cs
@@ -0,0 +1,48 @@
+using System.Security.Cryptography;
+
+using FrostFS.SDK.ClientV2.Interfaces;
+using FrostFS.SDK.Cryptography;
+
+using Microsoft.Extensions.Options;
+
+namespace FrostFS.SDK.Tests;
+
+public abstract class SessionTestsBase
+{
+ internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
+
+ protected IOptions Settings { get; set; }
+
+ protected ECDsa ECDsaKey { get; set; }
+ protected FrostFsOwner OwnerId { get; set; }
+ protected SessionMocker Mocker { get; set; }
+
+ protected SessionTestsBase()
+ {
+ Settings = Options.Create(new SingleOwnerClientSettings
+ {
+ Key = key,
+ Host = "http://localhost:8080"
+ });
+
+ ECDsaKey = key.LoadWif();
+ OwnerId = FrostFsOwner.FromKey(ECDsaKey);
+
+ Mocker = new SessionMocker(this.key)
+ {
+ PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
+ Version = new FrostFsVersion(2, 13)
+ };
+ }
+
+ protected IFrostFSClient GetClient()
+ {
+ return ClientV2.FrostFSClient.GetTestInstance(
+ Settings,
+ null,
+ new NetworkMocker(this.key).GetMock().Object,
+ Mocker.GetMock().Object,
+ new ContainerMocker(this.key).GetMock().Object,
+ new ObjectMocker(this.key).GetMock().Object);
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs
similarity index 88%
rename from src/FrostFS.SDK.Tests/SmokeTests.cs
rename to src/FrostFS.SDK.Tests/SmokeClientTests.cs
index 9c86ff9..54868d6 100644
--- a/src/FrostFS.SDK.Tests/SmokeTests.cs
+++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs
@@ -1,24 +1,42 @@
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
+
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.Cryptography;
+
using Microsoft.Extensions.Options;
using static FrostFS.Session.SessionToken.Types.Body;
-using FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.SmokeTests;
-public class SmokeTests : SmokeTestsBase
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
+[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
+public class SmokeClientTests : SmokeTestsBase
{
private static readonly PrmWait lightWait = new(100, 1);
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async void AccountTest(bool isSingleOnwerClient)
+ {
+ using var client = isSingleOnwerClient
+ ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
+ : FrostFSClient.GetInstance(GetOptions(this.url));
+
+ PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
+
+ var result = await client.GetBalanceAsync(prm);
+ }
+
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NetworkMapTest(bool isSingleOnwerClient)
{
- using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, 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 };
var result = await client.GetNetmapSnapshotAsync(prm);
@@ -41,7 +59,7 @@ public class SmokeTests : SmokeTestsBase
[InlineData(true)]
public async void NodeInfoTest(bool isSingleOnwerClient)
{
- using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, 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 };
@@ -56,14 +74,14 @@ public class SmokeTests : SmokeTestsBase
}
[Fact]
- public async void NodeInfo_Statistics_Test()
+ public async void NodeInfoStatisticsTest()
{
- var ctx = new Context
+ var ctx = new CallContext
{
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
};
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
var result = await client.GetNodeInfoAsync();
}
@@ -73,7 +91,7 @@ public class SmokeTests : SmokeTestsBase
[InlineData(true)]
public async void GetSessionTest(bool isSingleOnwerClient)
{
- using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, 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 };
@@ -81,7 +99,7 @@ public class SmokeTests : SmokeTestsBase
var session = new Session.SessionToken().Deserialize(token.Token);
- var ownerHash = Base58.Decode(OwnerId.Value);
+ var ownerHash = Base58.Decode(OwnerId!.Value);
Assert.NotNull(session);
Assert.Null(session.Body.Container);
@@ -96,7 +114,7 @@ public class SmokeTests : SmokeTestsBase
[Fact]
public async void CreateObjectWithSessionToken()
{
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
await Cleanup(client);
@@ -144,11 +162,7 @@ public class SmokeTests : SmokeTestsBase
[Fact]
public async void FilterTest()
{
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
-
- //var prm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Namespace, "root"));
-
- //var chains = await client.ListChainAsync(prm);
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
await Cleanup(client);
@@ -184,7 +198,7 @@ public class SmokeTests : SmokeTestsBase
var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
- var ecdsaKey = this.key.LoadWif();
+ var ecdsaKey = this.keyString.LoadWif();
var networkInfo = await client.GetNetmapSnapshotAsync();
@@ -192,7 +206,7 @@ public class SmokeTests : SmokeTestsBase
await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey)));
- await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split.SplitId));
+ await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId));
await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"));
@@ -232,12 +246,12 @@ public class SmokeTests : SmokeTestsBase
[InlineData(6 * 1024 * 1024 + 100)]
public async void SimpleScenarioTest(int objectSize)
{
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
await Cleanup(client);
bool callbackInvoked = false;
- var ctx = new Context
+ var ctx = new CallContext
{
// Timeout = TimeSpan.FromSeconds(20),
Callback = new((CallStatistics cs) =>
@@ -269,7 +283,7 @@ public class SmokeTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
- Context = new Context
+ Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
}
@@ -286,6 +300,7 @@ public class SmokeTests : SmokeTestsBase
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId));
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
Assert.Single(objHeader.Attributes);
Assert.Equal("fileName", objHeader.Attributes.First().Key);
Assert.Equal("test", objHeader.Attributes.First().Value);
@@ -304,7 +319,7 @@ public class SmokeTests : SmokeTestsBase
ms.Write(chunk.Value.Span);
}
- Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
await Cleanup(client);
@@ -320,13 +335,13 @@ public class SmokeTests : SmokeTestsBase
[InlineData(6 * 1024 * 1024 + 100)]
public async void SimpleScenarioWithSessionTest(int objectSize)
{
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
await Cleanup(client);
- var ctx = new Context
+ var ctx = new CallContext
{
Timeout = TimeSpan.FromSeconds(20),
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
@@ -353,7 +368,7 @@ public class SmokeTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
- Context = new Context
+ Context = new CallContext
{
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
},
@@ -371,6 +386,7 @@ public class SmokeTests : SmokeTestsBase
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token });
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
Assert.Single(objHeader.Attributes);
Assert.Equal("fileName", objHeader.Attributes.First().Key);
Assert.Equal("test", objHeader.Attributes.First().Value);
@@ -389,7 +405,7 @@ public class SmokeTests : SmokeTestsBase
ms.Write(chunk.Value.Span);
}
- Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
await Cleanup(client);
@@ -408,7 +424,7 @@ public class SmokeTests : SmokeTestsBase
[InlineData(200)]
public async void ClientCutScenarioTest(int objectSize)
{
- using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
await Cleanup(client);
@@ -419,7 +435,7 @@ public class SmokeTests : SmokeTestsBase
var containerId = await client.CreateContainerAsync(createContainerParam);
- var ctx = new Context
+ var ctx = new CallContext
{
Timeout = TimeSpan.FromSeconds(10),
Interceptors = new([new MetricsInterceptor()])
@@ -452,6 +468,7 @@ public class SmokeTests : SmokeTestsBase
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
+ Assert.NotNull(objHeader.Attributes);
Assert.Single(objHeader.Attributes);
Assert.Equal("fileName", objHeader.Attributes[0].Key);
Assert.Equal("test", objHeader.Attributes[0].Value);
@@ -470,7 +487,7 @@ public class SmokeTests : SmokeTestsBase
ms.Write(chunk.Value.Span);
}
- Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
+ Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
await CheckFilter(client, containerId, new FilterByRootObject());
diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs
index 5bf7733..6b42144 100644
--- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs
+++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs
@@ -7,24 +7,24 @@ namespace FrostFS.SDK.SmokeTests;
public abstract class SmokeTestsBase
{
- protected readonly string key = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
-
- protected readonly string url = "http://172.23.32.4:8080";
+ internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
- protected ECDsa Key { get; }
+ internal readonly string url = "http://172.23.32.4:8080";
- protected FrostFsOwner OwnerId { get; }
+ protected ECDsa? Key { get; }
- protected FrostFsVersion Version { get; }
+ protected FrostFsOwner? OwnerId { get; }
- protected Context Ctx { get; }
+ protected FrostFsVersion? Version { get; }
+
+ protected CallContext? Ctx { get; }
protected SmokeTestsBase()
{
- Key = key.LoadWif();
+ Key = keyString.LoadWif();
OwnerId = FrostFsOwner.FromKey(Key);
Version = new FrostFsVersion(2, 13);
- Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version };
+ Ctx = new CallContext { Key = Key, OwnerId = OwnerId, Version = Version };
}
}
--
2.45.2
From ee2079837906b274ccaf075a42cf5b55c5d1942b Mon Sep 17 00:00:00 2001
From: Pavel Gross
Date: Fri, 1 Nov 2024 10:30:28 +0300
Subject: [PATCH 2/3] [#24] Client: Implement pool part2
Signed-off-by: Pavel Gross
---
.../FrostFsInvalidObjectException.cs | 18 +
.../Exceptions/FrostFsResponseException.cs | 25 ++
.../Exceptions/FrostFsStreamException.cs | 18 +
.../Exceptions/InvalidObjectException.cs | 18 -
.../Exceptions/ResponseException.cs | 25 --
.../FrostFS.SDK.ClientV2.csproj | 4 +-
src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 81 ++---
.../Interceptors/ErrorInterceptor.cs | 68 ++++
.../Interceptors/MetricsInterceptor.cs | 14 +-
.../Interfaces/IFrostFSClient.cs | 2 -
.../Logging/FrostFsMessages.cs | 24 ++
.../Models/Client/ClientSettings.cs | 6 +-
.../Models/Containers/FrostFsContainerId.cs | 4 +-
.../Models/Containers/FrostFsContainerInfo.cs | 2 +-
.../Models/Object/FrostFsObject.cs | 4 +-
.../Parameters/CallContext.cs | 10 +-
.../Parameters/IContext.cs | 2 +-
.../Parameters/PrmApeChainList.cs | 2 +-
.../Parameters/PrmApeChainRemove.cs | 2 +-
.../Parameters/PrmApeRemoveAdd.cs | 2 +-
.../Parameters/PrmBalance.cs | 2 +-
.../Parameters/PrmBase.cs | 4 +-
.../Parameters/PrmContainerCreate.cs | 2 +-
.../Parameters/PrmContainerDelete.cs | 2 +-
.../Parameters/PrmContainerGet.cs | 2 +-
.../Parameters/PrmContainerGetAll.cs | 2 +-
.../Parameters/PrmNetmapSnapshot.cs | 2 +-
.../Parameters/PrmNetworkSettings.cs | 2 +-
.../Parameters/PrmNodeInfo.cs | 2 +-
.../Parameters/PrmObjectDelete.cs | 2 +-
.../Parameters/PrmObjectGet.cs | 2 +-
.../Parameters/PrmObjectHeadGet.cs | 2 +-
.../Parameters/PrmObjectPut.cs | 2 +-
.../Parameters/PrmObjectSearch.cs | 2 +-
.../Parameters/PrmSessionCreate.cs | 2 +-
.../Parameters/PrmSingleObjectPut.cs | 2 +-
.../Poll/ClientStatusMonitor.cs | 20 +-
.../Poll/ClientWrapper.cs | 72 ++--
src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs | 16 -
.../Poll/InitParameters.cs | 4 +-
src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs | 4 +-
src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 338 ++++++++++++------
.../Poll/RebalanceParameters.cs | 2 +-
src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs | 9 +-
.../Services/AccountingServiceProvider.cs | 2 +-
.../Services/ApeManagerServiceProvider.cs | 15 +-
.../Services/ContainerServiceProvider.cs | 36 +-
.../Services/NetmapServiceProvider.cs | 25 +-
.../Services/ObjectServiceProvider.cs | 52 +--
.../Services/SessionServiceProvider.cs | 4 +-
.../Services/Shared/ContextAccessor.cs | 4 +-
.../Services/Shared/SessionProvider.cs | 5 +-
...EnvironmentContext.cs => ClientContext.cs} | 22 +-
.../Tools/ObjectReader.cs | 6 +-
src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 2 +-
src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 4 +-
src/FrostFS.SDK.Tests/CallbackInterceptor.cs | 33 ++
src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 41 ---
src/FrostFS.SDK.Tests/NetworkTest.cs | 37 +-
src/FrostFS.SDK.Tests/ObjectTest.cs | 6 +-
src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 103 +++---
src/FrostFS.SDK.Tests/SessionTests.cs | 13 +-
src/FrostFS.SDK.Tests/SmokeClientTests.cs | 87 +++--
63 files changed, 801 insertions(+), 526 deletions(-)
create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs
delete mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs
delete mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs
create mode 100644 src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs
delete mode 100644 src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
rename src/FrostFS.SDK.ClientV2/Tools/{EnvironmentContext.cs => ClientContext.cs} (62%)
create mode 100644 src/FrostFS.SDK.Tests/CallbackInterceptor.cs
delete mode 100644 src/FrostFS.SDK.Tests/MetricsInterceptor.cs
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs
new file mode 100644
index 0000000..87e20e7
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs
@@ -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)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs
new file mode 100644
index 0000000..6ef30aa
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs
@@ -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)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs
new file mode 100644
index 0000000..45472e4
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs
@@ -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)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs
deleted file mode 100644
index c15e591..0000000
--- a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs
+++ /dev/null
@@ -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)
- {
- }
-}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs
deleted file mode 100644
index ce7f19a..0000000
--- a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs
+++ /dev/null
@@ -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)
- {
- }
-}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj
index b16e7e7..fefaaf2 100644
--- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj
+++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj
@@ -22,10 +22,10 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
index 2ca9e96..e13b6b6 100644
--- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
+++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs
@@ -39,7 +39,7 @@ public class FrostFSClient : IFrostFSClient
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
- internal EnvironmentContext ClientCtx { get; set; }
+ internal ClientContext ClientCtx { get; set; }
public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null)
{
@@ -93,7 +93,7 @@ public class FrostFSClient : IFrostFSClient
var ecdsaKey = settings.Value.Key.LoadWif();
FrostFsOwner.FromKey(ecdsaKey);
- ClientCtx = new EnvironmentContext(
+ ClientCtx = new ClientContext(
client: this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@@ -108,13 +108,13 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions)
{
- var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
+ var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
- ClientCtx = new EnvironmentContext(
+ ClientCtx = new ClientContext(
this,
key: null,
owner: null,
@@ -127,7 +127,7 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions)
{
- var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
+ var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
@@ -135,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
- ClientCtx = new EnvironmentContext(
+ ClientCtx = new ClientContext(
this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@@ -146,14 +146,17 @@ public class FrostFSClient : IFrostFSClient
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
}
- internal FrostFSClient(WrapperPrm prm)
+ internal FrostFSClient(WrapperPrm prm, SessionCache cache)
{
- ClientCtx = new EnvironmentContext(
+ ClientCtx = new ClientContext(
client: this,
key: prm.Key,
owner: FrostFsOwner.FromKey(prm.Key!),
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
- version: new FrostFsVersion(2, 13));
+ version: new FrostFsVersion(2, 13))
+ {
+ SessionCache = cache
+ };
}
public void Dispose()
@@ -363,10 +366,10 @@ public class FrostFSClient : IFrostFSClient
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
{
- var args = new PrmNodeInfo { Context = ctx };
+ var args = new PrmNodeInfo(ctx);
if (ctx?.Version == null)
- throw new InvalidObjectException(nameof(ctx.Version));
+ throw new ArgumentNullException(nameof(ctx), "Version must be initialized");
var service = GetNetmapService(args);
var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false);
@@ -378,18 +381,16 @@ public class FrostFSClient : IFrostFSClient
}
}
- private CallInvoker? SetupEnvironment(IContext ctx)
+ private CallInvoker? SetupClientContext(IContext ctx)
{
if (isDisposed)
- throw new InvalidObjectException("Client is disposed.");
+ throw new FrostFsInvalidObjectException("Client is disposed.");
- ctx.Context ??= new CallContext();
-
- if (ctx.Context.Key == null)
+ if (ctx.Context!.Key == null)
{
if (ClientCtx.Key == null)
{
- throw new InvalidObjectException("Key is not initialized.");
+ throw new ArgumentNullException(nameof(ctx), "Key is not initialized.");
}
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
@@ -404,24 +405,23 @@ public class FrostFSClient : IFrostFSClient
{
if (ClientCtx.Version == null)
{
- throw new InvalidObjectException("Version is not initialized.");
+ throw new ArgumentNullException(nameof(ctx), "Version is not initialized.");
}
ctx.Context.Version = ClientCtx.Version;
}
CallInvoker? callInvoker = null;
- if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0)
- {
- foreach (var interceptor in ctx.Context.Interceptors)
- {
- callInvoker = AddInvoker(callInvoker, interceptor);
- }
- }
+
+ foreach (var interceptor in ctx.Context.Interceptors)
+ callInvoker = AddInvoker(callInvoker, interceptor);
if (ctx.Context.Callback != null)
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
+ if (ctx.Context.PoolErrorHandler != null)
+ callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler));
+
return callInvoker;
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
@@ -429,7 +429,7 @@ public class FrostFSClient : IFrostFSClient
if (callInvoker == null)
callInvoker = ClientCtx.Channel.Intercept(interceptor);
else
- callInvoker.Intercept(interceptor);
+ callInvoker = callInvoker.Intercept(interceptor);
return callInvoker;
}
@@ -437,7 +437,7 @@ public class FrostFSClient : IFrostFSClient
private NetmapServiceProvider GetNetmapService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = NetmapServiceClient ?? (callInvoker != null
? new NetmapService.NetmapServiceClient(callInvoker)
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
@@ -447,7 +447,7 @@ public class FrostFSClient : IFrostFSClient
private SessionServiceProvider GetSessionService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = SessionServiceClient ?? (callInvoker != null
? new SessionService.SessionServiceClient(callInvoker)
: new SessionService.SessionServiceClient(ClientCtx.Channel));
@@ -457,7 +457,7 @@ public class FrostFSClient : IFrostFSClient
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = ApeManagerServiceClient ?? (callInvoker != null
? new APEManagerService.APEManagerServiceClient(callInvoker)
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
@@ -467,7 +467,7 @@ public class FrostFSClient : IFrostFSClient
private AccountingServiceProvider GetAccouningService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = AccountingServiceClient ?? (callInvoker != null
? new AccountingService.AccountingServiceClient(callInvoker)
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
@@ -477,7 +477,7 @@ public class FrostFSClient : IFrostFSClient
private ContainerServiceProvider GetContainerService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = ContainerServiceClient ?? (callInvoker != null
? new ContainerService.ContainerServiceClient(callInvoker)
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
@@ -487,7 +487,7 @@ public class FrostFSClient : IFrostFSClient
private ObjectServiceProvider GetObjectService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = ObjectServiceClient ?? (callInvoker != null
? new ObjectService.ObjectServiceClient(callInvoker)
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
@@ -497,7 +497,7 @@ public class FrostFSClient : IFrostFSClient
private AccountingServiceProvider GetAccountService(IContext ctx)
{
- var callInvoker = SetupEnvironment(ctx);
+ var callInvoker = SetupClientContext(ctx);
var client = AccountingServiceClient ?? (callInvoker != null
? new AccountingService.AccountingServiceClient(callInvoker)
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
@@ -527,19 +527,12 @@ public class FrostFSClient : IFrostFSClient
public async Task Dial(CallContext ctx)
{
- try
- {
- var prm = new PrmBalance { Context = ctx };
+ var prm = new PrmBalance(ctx);
- var service = GetAccouningService(prm);
- var balance = await service.GetBallance(prm).ConfigureAwait(false);
+ var service = GetAccouningService(prm);
+ _ = await service.GetBallance(prm).ConfigureAwait(false);
- return null;
- }
- catch (FrostFsException ex)
- {
- return ex.Message;
- }
+ return null;
}
public bool RestartIfUnhealthy(CallContext ctx)
diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs
new file mode 100644
index 0000000..d76477f
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs
@@ -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 handler) : Interceptor
+{
+ public override AsyncUnaryCall AsyncUnaryCall(
+ TRequest request,
+ ClientInterceptorContext context,
+ AsyncUnaryCallContinuation continuation)
+ {
+ var call = continuation(request, context);
+
+ return new AsyncUnaryCall(
+ HandleUnaryResponse(call),
+ call.ResponseHeadersAsync,
+ call.GetStatus,
+ call.GetTrailers,
+ call.Dispose);
+ }
+
+ public override AsyncClientStreamingCall AsyncClientStreamingCall(
+ ClientInterceptorContext context,
+ AsyncClientStreamingCallContinuation continuation)
+ {
+ var call = continuation(context);
+
+ return new AsyncClientStreamingCall(
+ call.RequestStream,
+ HandleStreamResponse(call),
+ call.ResponseHeadersAsync,
+ call.GetStatus,
+ call.GetTrailers,
+ call.Dispose);
+ }
+
+ private async Task HandleUnaryResponse(AsyncUnaryCall call)
+ {
+ try
+ {
+ return await call;
+ }
+ catch (Exception ex)
+ {
+ handler(ex);
+ throw;
+ }
+ }
+
+ private async Task HandleStreamResponse(AsyncClientStreamingCall call)
+ {
+ try
+ {
+ return await call;
+ }
+ catch (Exception ex)
+ {
+ handler(ex);
+ throw;
+ }
+ }
+}
diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs
index 9b3199c..f4d93c0 100644
--- a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs
+++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs
@@ -7,6 +7,8 @@ using Grpc.Core.Interceptors;
namespace FrostFS.SDK.ClientV2;
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
+ Justification = "parameters are provided by GRPC infrastructure")]
public class MetricsInterceptor(Action callback) : Interceptor
{
public override AsyncUnaryCall AsyncUnaryCall(
@@ -14,11 +16,6 @@ public class MetricsInterceptor(Action callback) : Interceptor
ClientInterceptorContext context,
AsyncUnaryCallContinuation continuation)
{
- if (continuation is null)
- {
- throw new ArgumentNullException(nameof(continuation));
- }
-
var call = continuation(request, context);
return new AsyncUnaryCall(
@@ -33,9 +30,6 @@ public class MetricsInterceptor(Action callback) : Interceptor
ClientInterceptorContext context,
AsyncClientStreamingCallContinuation continuation)
{
- if (continuation is null)
- throw new ArgumentNullException(nameof(continuation));
-
var call = continuation(context);
return new AsyncClientStreamingCall(
@@ -52,7 +46,7 @@ public class MetricsInterceptor(Action callback) : Interceptor
var watch = new Stopwatch();
watch.Start();
- var response = await call.ResponseAsync.ConfigureAwait(false);
+ var response = await call;
watch.Stop();
@@ -68,7 +62,7 @@ public class MetricsInterceptor(Action callback) : Interceptor
var watch = new Stopwatch();
watch.Start();
- var response = await call.ResponseAsync.ConfigureAwait(false);
+ var response = await call;
watch.Stop();
diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
index 193d128..e81343b 100644
--- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
+++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs
@@ -61,7 +61,5 @@ public interface IFrostFSClient : IDisposable
public Task Dial(CallContext ctx);
- public bool RestartIfUnhealthy(CallContext ctx);
-
public void Close();
}
diff --git a/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs b/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs
new file mode 100644
index 0000000..2fd1fe1
--- /dev/null
+++ b/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs
@@ -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);
+}
diff --git a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs
index b1faf79..6ebec1f 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs
@@ -15,7 +15,7 @@ public class ClientSettings
{
var errors = CheckFields();
if (errors != null)
- ThrowException(errors);
+ ThrowSettingsException(errors);
}
protected Collection? CheckFields()
@@ -29,7 +29,7 @@ public class ClientSettings
return null;
}
- protected static void ThrowException(Collection errors)
+ protected static void ThrowSettingsException(Collection errors)
{
if (errors is null)
{
@@ -55,7 +55,7 @@ public class SingleOwnerClientSettings : ClientSettings
{
var errors = CheckFields();
if (errors != null)
- ThrowException(errors);
+ ThrowSettingsException(errors);
}
protected new Collection? CheckFields()
diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs
index 1ebbe04..06b87ca 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs
@@ -31,7 +31,7 @@ public class FrostFsContainerId
return this.modelId;
}
- throw new InvalidObjectException();
+ throw new FrostFsInvalidObjectException();
}
internal ContainerID ContainerID
@@ -47,7 +47,7 @@ public class FrostFsContainerId
return this.containerID;
}
- throw new InvalidObjectException();
+ throw new FrostFsInvalidObjectException();
}
}
diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs
index e7336b2..affdbee 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs
@@ -88,7 +88,7 @@ public class FrostFsContainerInfo
{
if (PlacementPolicy == null)
{
- throw new InvalidObjectException("PlacementPolicy is null");
+ throw new ArgumentNullException("PlacementPolicy is null");
}
this.container = new Container.Container()
diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs
index a6c82db..f366b9e 100644
--- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs
+++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs
@@ -1,4 +1,4 @@
-using FrostFS.SDK.ClientV2;
+using System;
namespace FrostFS.SDK;
@@ -67,7 +67,7 @@ public class FrostFsObject
public void SetParent(FrostFsObjectHeader largeObjectHeader)
{
if (Header?.Split == null)
- throw new InvalidObjectException("The object is not initialized properly");
+ throw new ArgumentNullException(nameof(largeObjectHeader), "Split value must not be null");
Header.Split.ParentHeader = largeObjectHeader;
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
index ff4bc27..24f076e 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs
@@ -13,10 +13,10 @@ namespace FrostFS.SDK.ClientV2;
public class CallContext()
{
- private ReadOnlyCollection? interceptors;
-
private ByteString? publicKeyCache;
+ internal Action? PoolErrorHandler { get; set; }
+
public ECDsa? Key { get; set; }
public FrostFsOwner? OwnerId { get; set; }
@@ -31,11 +31,7 @@ public class CallContext()
public Action? Callback { get; set; }
- public ReadOnlyCollection? Interceptors
- {
- get { return this.interceptors; }
- set { this.interceptors = value; }
- }
+ public Collection Interceptors { get; } = [];
public ByteString? GetPublicKeyCache()
{
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
index 8c6f1df..128c6a0 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
@@ -7,5 +7,5 @@ public interface IContext
/// callbacks, interceptors.
///
/// Additional parameters for calling the method
- CallContext? Context { get; set; }
+ CallContext? Context { get; }
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs
index a68e788..4202685 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmApeChainList(FrostFsChainTarget target) : PrmBase
+public sealed class PrmApeChainList(FrostFsChainTarget target, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs
index 9371b43..a7a9f5a 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain) : PrmBase
+public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs
index 330f601..75ac0e7 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain) : PrmBase
+public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsChainTarget Target { get; } = target;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
index 3a07fde..df36980 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
@@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmBalance() : PrmBase
+public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx)
{
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
index 79e5e7e..e6fb030 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs
@@ -2,7 +2,7 @@
namespace FrostFS.SDK.ClientV2;
-public class PrmBase(NameValueCollection? xheaders = null) : IContext
+public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext
{
///
/// FrostFS request X-Headers
@@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext
public NameValueCollection XHeaders { get; } = xheaders ?? [];
///
- public CallContext? Context { get; set; }
+ public CallContext Context { get; } = ctx ?? new CallContext();
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs
index da4c50b..68eadc9 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmContainerCreate(FrostFsContainerInfo container) : PrmBase, ISessionToken
+public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerInfo Container { get; set; } = container;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs
index c97e80f..9de85f8 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmContainerDelete(FrostFsContainerId containerId) : PrmBase, ISessionToken
+public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs
index f0c734b..85f0821 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmContainerGet(FrostFsContainerId container) : PrmBase
+public sealed class PrmContainerGet(FrostFsContainerId container, CallContext? ctx = null) : PrmBase(ctx)
{
public FrostFsContainerId Container { get; set; } = container;
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs
index b7d3980..ceec0c1 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs
@@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmContainerGetAll() : PrmBase()
+public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx)
{
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs
index 6031ca6..0938f86 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs
@@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmNetmapSnapshot() : PrmBase
+public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx)
{
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs
index f014f14..de2c13d 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs
@@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmNetworkSettings() : PrmBase
+public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx)
{
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs
index 05b541d..dbbcdd8 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs
@@ -1,5 +1,5 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmNodeInfo() : PrmBase
+public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx)
{
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs
index f1dad65..e168b31 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
+public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs
index 81a1e2f..11e64d7 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
+public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs
index 40410f3..0b948b7 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken
+public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; set; } = containerId;
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs
index 3d216d5..47a1e1e 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs
@@ -2,7 +2,7 @@ using System.IO;
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmObjectPut : PrmBase, ISessionToken
+public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
///
/// Need to provide values like ContainerId and ObjectType to create and object.
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs
index a9310dd..723c1e7 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs
@@ -2,7 +2,7 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmObjectSearch(FrostFsContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken
+public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken
{
///
/// Defines container for the search
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs
index d7bcdb6..ab02f69 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmSessionCreate(ulong expiration) : PrmBase
+public sealed class PrmSessionCreate(ulong expiration, CallContext? ctx = null) : PrmBase(ctx)
{
public ulong Expiration { get; set; } = expiration;
}
diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs
index 3a8fa5d..8cb8c19 100644
--- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs
+++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken
+public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsObject FrostFsObject { get; set; } = frostFsObject;
diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
index e864148..7200b37 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
@@ -27,8 +27,7 @@ public class ClientStatusMonitor : IClientStatus
MethodIndex.methodSessionCreate,
MethodIndex.methodAPEManagerAddChain,
MethodIndex.methodAPEManagerRemoveChain,
- MethodIndex.methodAPEManagerListChains,
- MethodIndex.methodLast
+ MethodIndex.methodAPEManagerListChains
];
public static string GetMethodName(MethodIndex index)
@@ -53,7 +52,7 @@ public class ClientStatusMonitor : IClientStatus
MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
MethodIndex.methodAPEManagerListChains => "APEManagerListChains",
- _ => throw new NotImplementedException(),
+ _ => throw new ArgumentException("Unknown method", nameof(index)),
};
}
@@ -62,13 +61,12 @@ public class ClientStatusMonitor : IClientStatus
private readonly ILogger? logger;
private int healthy;
- public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold)
+ public ClientStatusMonitor(ILogger? logger, string address)
{
this.logger = logger;
healthy = (int)HealthyStatus.Healthy;
- Address = address;
- ErrorThreshold = errorThreshold;
+ Address = address;
Methods = new MethodStatus[MethodIndexes.Length];
for (int i = 0; i < MethodIndexes.Length; i++)
@@ -79,7 +77,7 @@ public class ClientStatusMonitor : IClientStatus
public string Address { get; }
- internal uint ErrorThreshold { get; }
+ internal uint ErrorThreshold { get; set; }
public uint CurrentErrorCount { get; set; }
@@ -89,7 +87,8 @@ public class ClientStatusMonitor : IClientStatus
public bool IsHealthy()
{
- return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
+ var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
+ return res;
}
public bool IsDialed()
@@ -124,14 +123,13 @@ public class ClientStatusMonitor : IClientStatus
if (thresholdReached)
{
SetUnhealthy();
-
CurrentErrorCount = 0;
}
}
- if (thresholdReached)
+ if (thresholdReached && logger != null)
{
- logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold);
+ FrostFsMessages.ErrorЕhresholdReached(logger, Address, ErrorThreshold);
}
}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
index 3891250..ddde002 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
@@ -1,38 +1,36 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
+
+using Grpc.Core;
namespace FrostFS.SDK.ClientV2;
// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
-public class ClientWrapper
+public class ClientWrapper : ClientStatusMonitor
{
private readonly object _lock = new();
- public ClientWrapper(WrapperPrm wrapperPrm)
+ private SessionCache sessionCache;
+
+
+ internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address)
{
WrapperPrm = wrapperPrm;
- StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold);
+ ErrorThreshold = wrapperPrm.ErrorThreshold;
- try
- {
- Client = new FrostFSClient(WrapperPrm);
- StatusMonitor.SetHealthy();
- }
- catch (FrostFsException)
- {
- }
+ sessionCache = pool.SessionCache;
+ Client = new FrostFSClient(WrapperPrm, sessionCache);
}
internal FrostFSClient? Client { get; private set; }
internal WrapperPrm WrapperPrm { get; }
- internal ClientStatusMonitor StatusMonitor { get; }
-
internal FrostFSClient? GetClient()
{
lock (_lock)
{
- if (StatusMonitor.IsHealthy())
+ if (IsHealthy())
{
return Client;
}
@@ -44,21 +42,29 @@ public class ClientWrapper
// dial establishes a connection to the server from the FrostFS network.
// Returns an error describing failure reason. If failed, the client
// SHOULD NOT be used.
- internal async Task Dial(CallContext ctx)
+ internal async Task Dial(CallContext ctx)
{
- var client = GetClient();
+ var client = GetClient() ?? throw new FrostFsInvalidObjectException("pool client unhealthy");
- if (client == null)
- return "pool client unhealthy";
+ await client.Dial(ctx).ConfigureAwait(false);
+ }
- var result = await client.Dial(ctx).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(result))
+ internal void HandleError(Exception ex)
+ {
+ if (ex is FrostFsResponseException responseException && responseException.Status != null)
{
- StatusMonitor.SetUnhealthyOnDial();
- return result;
+ switch (responseException.Status.Code)
+ {
+ case FrostFsStatusCode.Internal:
+ case FrostFsStatusCode.WrongMagicNumber:
+ case FrostFsStatusCode.SignatureVerificationFailure:
+ case FrostFsStatusCode.NodeUnderMaintenance:
+ IncErrorRate();
+ return;
+ }
}
- return null;
+ IncErrorRate();
}
private async Task ScheduleGracefulClose()
@@ -79,31 +85,31 @@ public class ClientWrapper
try
{
- var prmNodeInfo = new PrmNodeInfo { Context = ctx };
+ var prmNodeInfo = new PrmNodeInfo(ctx);
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
return false;
}
- catch (FrostFsException)
+ catch (RpcException)
{
wasHealthy = true;
}
// if connection is dialed before, to avoid routine/connection leak,
// pool has to close it and then initialize once again.
- if (StatusMonitor.IsDialed())
+ if (IsDialed())
{
await ScheduleGracefulClose().ConfigureAwait(false);
}
#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
- FrostFSClient client = new(WrapperPrm);
+ FrostFSClient client = new(WrapperPrm, sessionCache);
#pragma warning restore CA2000
//TODO: set additioanl params
var error = await client.Dial(ctx).ConfigureAwait(false);
if (!string.IsNullOrEmpty(error))
{
- StatusMonitor.SetUnhealthyOnDial();
+ SetUnhealthyOnDial();
return wasHealthy;
}
@@ -114,22 +120,22 @@ public class ClientWrapper
try
{
- var prmNodeInfo = new PrmNodeInfo { Context = ctx };
+ var prmNodeInfo = new PrmNodeInfo(ctx);
var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
}
catch (FrostFsException)
{
- StatusMonitor.SetUnhealthy();
+ SetUnhealthy();
return wasHealthy;
}
- StatusMonitor.SetHealthy();
+ SetHealthy();
return !wasHealthy;
}
internal void IncRequests(ulong elapsed, MethodIndex method)
{
- var methodStat = StatusMonitor.Methods[(int)method];
+ var methodStat = Methods[(int)method];
methodStat.IncRequests(elapsed);
}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
deleted file mode 100644
index 813d373..0000000
--- a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
+++ /dev/null
@@ -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; }
-}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
index e05c31a..f02bd9e 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
@@ -1,6 +1,8 @@
using System;
using System.Security.Cryptography;
+using Grpc.Net.Client;
+
using Microsoft.Extensions.Logging;
namespace FrostFS.SDK.ClientV2;
@@ -24,7 +26,7 @@ public class InitParameters
public NodeParam[]? NodeParams { get; set; }
- public DialOptions[]? DialOptions { get; set; }
+ public GrpcChannelOptions[]? DialOptions { get; set; }
public Func? ClientBuilder { get; set; }
diff --git a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
index a535260..104f1f1 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
@@ -21,7 +21,7 @@ internal sealed class InnerPool
if (Clients.Length == 1)
{
var client = Clients[0];
- if (client.StatusMonitor.IsHealthy())
+ if (client.IsHealthy())
{
return client;
}
@@ -34,7 +34,7 @@ internal sealed class InnerPool
{
int index = Sampler.Next();
- if (Clients[index].StatusMonitor.IsHealthy())
+ if (Clients[index].IsHealthy())
{
return Clients[index];
}
diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
index a304e93..2602746 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -13,6 +12,8 @@ using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
+using Grpc.Core;
+
using Microsoft.Extensions.Logging;
@@ -36,7 +37,7 @@ public partial class Pool : IFrostFSClient
private ECDsa Key { get; set; }
- private byte[] PublicKey { get; }
+ private string PublicKey { get; }
private OwnerID? _ownerId;
private FrostFsOwner? _owner;
@@ -65,7 +66,7 @@ public partial class Pool : IFrostFSClient
internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
- private SessionCache Cache { get; set; }
+ internal SessionCache SessionCache { get; set; }
private ulong SessionTokenDuration { get; set; }
@@ -75,7 +76,7 @@ public partial class Pool : IFrostFSClient
private bool disposedValue;
- private ILogger? Logger { get; set; }
+ private ILogger? logger { get; set; }
private ulong MaxObjectSize { get; set; }
@@ -91,20 +92,20 @@ public partial class Pool : IFrostFSClient
if (options.Key == null)
{
- throw new FrostFsException($"Missed required parameter {nameof(options.Key)}");
+ throw new ArgumentException($"Missed required parameter {nameof(options.Key)}");
}
var nodesParams = AdjustNodeParams(options.NodeParams);
var cache = new SessionCache(options.SessionExpirationDuration);
- FillDefaultInitParams(options, cache);
+ FillDefaultInitParams(options, this);
Key = options.Key;
- PublicKey = Key.PublicKey();
+ PublicKey = $"{Key.PublicKey()}";
- Cache = cache;
- Logger = options.Logger;
+ SessionCache = cache;
+ logger = options.Logger;
SessionTokenDuration = options.SessionExpirationDuration;
RebalanceParams = new RebalanceParameters(
@@ -148,47 +149,54 @@ public partial class Pool : IFrostFSClient
for (int j = 0; j < nodeParams.Addresses.Count; j++)
{
- var client = ClientBuilder(nodeParams.Addresses[j]);
- clients[j] = client;
-
- var error = await client.Dial(ctx).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(error))
- {
- Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error);
- continue;
- }
-
+ ClientWrapper? client = null;
+ bool dialed = false;
try
{
+ client = clients[j] = ClientBuilder(nodeParams.Addresses[j]);
+
+ await client.Dial(ctx).ConfigureAwait(false);
+ dialed = true;
+
var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false)
.ConfigureAwait(false);
- var key = FormCacheKey(nodeParams.Addresses[j], Key, false);
- _ = Cache.Cache[key] = token;
+ var key = FormCacheKey(nodeParams.Addresses[j], Key.PrivateKey().ToString());
+ _ = SessionCache.Cache[key] = token;
+
+ atLeastOneHealthy = true;
}
- catch (FrostFsException ex)
+ catch (RpcException ex)
{
- client.StatusMonitor.SetUnhealthy();
- Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})",
- client.WrapperPrm.Address, ex.Message);
+ if (!dialed)
+ client!.SetUnhealthyOnDial();
+ else
+ client!.SetUnhealthy();
- continue;
+ if (logger != null)
+ {
+ FrostFsMessages.SessionCreationError(logger, client!.WrapperPrm.Address, ex.Message);
+ }
+ }
+ catch (FrostFsInvalidObjectException)
+ {
+ break;
}
-
- atLeastOneHealthy = true;
}
var sampler = new Sampler(nodeParams.Weights.ToArray());
inner[i] = new InnerPool(sampler, clients);
+
+ i++;
}
if (!atLeastOneHealthy)
- return "at least one node must be healthy";
+ return "At least one node must be healthy";
InnerPools = inner;
- var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
+ var res = await GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false);
MaxObjectSize = res.MaxObjectSize;
@@ -252,7 +260,7 @@ public partial class Pool : IFrostFSClient
return adjusted;
}
- private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache)
+ private static void FillDefaultInitParams(InitParameters parameters, Pool pool)
{
if (parameters.SessionExpirationDuration == 0)
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
@@ -275,8 +283,8 @@ public partial class Pool : IFrostFSClient
if (parameters.NodeStreamTimeout <= 0)
parameters.NodeStreamTimeout = defaultStreamTimeout;
- if (cache.TokenDuration == 0)
- cache.TokenDuration = defaultSessionTokenExpirationDuration;
+ if (parameters.SessionExpirationDuration == 0)
+ parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
parameters.ClientBuilder ??= new Func((address) =>
{
@@ -291,29 +299,29 @@ public partial class Pool : IFrostFSClient
GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
};
- return new ClientWrapper(wrapperPrm);
+ return new ClientWrapper(wrapperPrm, pool);
}
);
}
- private FrostFSClient? Сonnection()
+ private ClientWrapper Сonnection()
{
foreach (var pool in InnerPools!)
{
var client = pool.Connection();
if (client != null)
{
- return client.Client;
+ return client;
}
}
- return null;
+ throw new FrostFsException("Cannot find alive client");
}
private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut)
{
var client = cw.Client;
- var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
+ var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false);
var epoch = networkInfo.Epoch;
@@ -321,17 +329,14 @@ public partial class Pool : IFrostFSClient
? ulong.MaxValue
: epoch + duration;
- var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx };
+ var prmSessionCreate = new PrmSessionCreate(exp, ctx);
return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false);
}
- private static string FormCacheKey(string address, ECDsa key, bool clientCut)
+ internal static string FormCacheKey(string address, string key)
{
- var k = key.PrivateKey;
- var stype = clientCut ? "client" : "server";
-
- return $"{address}{stype}{k}";
+ return $"{address}{key}";
}
public void Close()
@@ -343,7 +348,7 @@ public partial class Pool : IFrostFSClient
// close all clients
foreach (var innerPool in InnerPools)
foreach (var client in innerPool.Clients)
- if (client.StatusMonitor.IsDialed())
+ if (client.IsDialed())
client.Client?.Close();
}
}
@@ -355,7 +360,7 @@ public partial class Pool : IFrostFSClient
for (int i = 0; i < RebalanceParams.NodesParams.Length; i++)
{
- var parameters = this.RebalanceParams.NodesParams[i];
+ var parameters = RebalanceParams.NodesParams[i];
buffers[i] = new double[parameters.Weights.Count];
Task.Run(async () =>
@@ -405,25 +410,27 @@ public partial class Pool : IFrostFSClient
try
{
// check timeout settings
- changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false);
+ changed = await client!.RestartIfUnhealthy(ctx).ConfigureAwait(false);
healthy = true;
bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
}
+ // TODO: specify
catch (FrostFsException e)
{
error = e.Message;
bufferWeights[j] = 0;
- Cache.DeleteByPrefix(client.StatusMonitor.Address);
+ SessionCache.DeleteByPrefix(client.Address);
}
if (changed)
{
- StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}");
- if (string.IsNullOrEmpty(error))
+ if (!string.IsNullOrEmpty(error))
{
- fields.Append($", reason {error}");
- Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString());
+ if (logger != null)
+ {
+ FrostFsMessages.HealthChanged(logger, client.Address, healthy, error!);
+ }
Interlocked.Exchange(ref healthyChanged, 1);
}
@@ -443,6 +450,8 @@ public partial class Pool : IFrostFSClient
}
}
+
+ // TODO: remove
private bool CheckSessionTokenErr(Exception error, string address)
{
if (error == null)
@@ -452,7 +461,7 @@ public partial class Pool : IFrostFSClient
if (error is SessionNotFoundException || error is SessionExpiredException)
{
- this.Cache.DeleteByPrefix(address);
+ this.SessionCache.DeleteByPrefix(address);
return true;
}
@@ -463,14 +472,13 @@ public partial class Pool : IFrostFSClient
{
if (InnerPools == null)
{
- throw new InvalidObjectException(nameof(Pool));
+ throw new FrostFsInvalidObjectException(nameof(Pool));
}
var statistics = new Statistic();
foreach (var inner in InnerPools)
{
- int nodeIndex = 0;
int valueIndex = 0;
var nodes = new string[inner.Clients.Length];
@@ -478,20 +486,22 @@ public partial class Pool : IFrostFSClient
{
foreach (var client in inner.Clients)
{
- if (client.StatusMonitor.IsHealthy())
+ if (client.IsHealthy())
{
- nodes[valueIndex++] = client.StatusMonitor.Address;
+ nodes[valueIndex] = client.Address;
}
var node = new NodeStatistic
{
- Address = client.StatusMonitor.Address,
- Methods = client.StatusMonitor.MethodsStatus(),
- OverallErrors = client.StatusMonitor.GetOverallErrorRate(),
- CurrentErrors = client.StatusMonitor.GetCurrentErrorRate()
+ Address = client.Address,
+ Methods = client.MethodsStatus(),
+ OverallErrors = client.GetOverallErrorRate(),
+ CurrentErrors = client.GetCurrentErrorRate()
};
- statistics.Nodes[nodeIndex++] = node;
+ statistics.Nodes.Add(node);
+
+ valueIndex++;
statistics.OverallErrors += node.OverallErrors;
}
@@ -508,120 +518,234 @@ public partial class Pool : IFrostFSClient
public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
+ var client = Сonnection();
+
+ args ??= new();
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
}
public async Task GetNodeInfoAsync(PrmNodeInfo? args = null)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetNodeInfoAsync(args).ConfigureAwait(false);
+ var client = Сonnection();
+
+ args ??= new();
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false);
}
public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false);
+ var client = Сonnection();
+
+ args ??= new();
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false);
}
public async Task CreateSessionAsync(PrmSessionCreate args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.CreateSessionAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.CreateSessionAsync(args).ConfigureAwait(false);
}
public async Task AddChainAsync(PrmApeChainAdd args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.AddChainAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.AddChainAsync(args).ConfigureAwait(false);
}
public async Task RemoveChainAsync(PrmApeChainRemove args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- await client.RemoveChainAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ await client.Client!.RemoveChainAsync(args).ConfigureAwait(false);
}
public async Task ListChainAsync(PrmApeChainList args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.ListChainAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.ListChainAsync(args).ConfigureAwait(false);
}
public async Task GetContainerAsync(PrmContainerGet args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetContainerAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetContainerAsync(args).ConfigureAwait(false);
}
public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return client.ListContainersAsync(args);
+ var client = Сonnection();
+
+ args ??= new();
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return client.Client!.ListContainersAsync(args);
}
public async Task CreateContainerAsync(PrmContainerCreate args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.CreateContainerAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.CreateContainerAsync(args).ConfigureAwait(false);
}
public async Task DeleteContainerAsync(PrmContainerDelete args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- await client.DeleteContainerAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ await client.Client!.DeleteContainerAsync(args).ConfigureAwait(false);
}
public async Task GetObjectHeadAsync(PrmObjectHeadGet args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetObjectHeadAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetObjectHeadAsync(args).ConfigureAwait(false);
}
public async Task GetObjectAsync(PrmObjectGet args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetObjectAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.GetObjectAsync(args).ConfigureAwait(false);
}
public async Task PutObjectAsync(PrmObjectPut args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.PutObjectAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.PutObjectAsync(args).ConfigureAwait(false);
}
public async Task PutSingleObjectAsync(PrmSingleObjectPut args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.PutSingleObjectAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false);
}
public async Task DeleteObjectAsync(PrmObjectDelete args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- await client.DeleteObjectAsync(args).ConfigureAwait(false);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ await client.Client!.DeleteObjectAsync(args).ConfigureAwait(false);
}
public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return client.SearchObjectsAsync(args);
+ if (args is null)
+ {
+ throw new ArgumentNullException(nameof(args));
+ }
+
+ var client = Сonnection();
+
+ args.Context.PoolErrorHandler = client.HandleError;
+
+ return client.Client!.SearchObjectsAsync(args);
}
- public async Task GetBalanceAsync(PrmBalance? args = null)
+ public async Task GetBalanceAsync(PrmBalance? args)
{
- var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
- return await client.GetBalanceAsync(args).ConfigureAwait(false);
- }
+ var client = Сonnection();
- public bool RestartIfUnhealthy(CallContext ctx)
- {
- throw new NotImplementedException();
- }
+ args ??= new();
+ args.Context.PoolErrorHandler = client.HandleError;
- public bool IsHealthy()
- {
- throw new NotImplementedException();
+ return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false);
}
protected virtual void Dispose(bool disposing)
diff --git a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
index b1bc9b9..a443b2d 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
@@ -3,7 +3,7 @@
public class RebalanceParameters(
NodesParam[] nodesParams,
ulong nodeRequestTimeout,
- ulong clientRebalanceInterval,
+ ulong clientRebalanceInterval,
ulong sessionExpirationDuration)
{
public NodesParam[] NodesParams { get; set; } = nodesParams;
diff --git a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
index eccb0a5..f65046f 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
@@ -3,18 +3,13 @@ using System.Collections;
namespace FrostFS.SDK.ClientV2;
-internal struct SessionCache
+internal struct SessionCache(ulong sessionExpirationDuration)
{
- public SessionCache(ulong sessionExpirationDuration)
- {
- TokenDuration = sessionExpirationDuration;
- }
-
internal Hashtable Cache { get; } = [];
internal ulong CurrentEpoch { get; set; }
- internal ulong TokenDuration { get; set; }
+ internal ulong TokenDuration { get; set; } = sessionExpirationDuration;
internal void DeleteByPrefix(string prefix)
{
diff --git a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
index 94dda54..b5fa42b 100644
--- a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs
@@ -10,7 +10,7 @@ internal sealed class AccountingServiceProvider : ContextAccessor
internal AccountingServiceProvider(
AccountingService.AccountingServiceClient? accountingServiceClient,
- EnvironmentContext context)
+ ClientContext context)
: base(context)
{
_accountingServiceClient = accountingServiceClient;
diff --git a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
index 1c5ab8c..d05e636 100644
--- a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading.Tasks;
using Frostfs.V2.Ape;
@@ -9,7 +10,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
{
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
- internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context)
+ internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientContext context)
: base(context)
{
_apeManagerServiceClient = apeManagerServiceClient;
@@ -18,10 +19,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task AddChainAsync(PrmApeChainAdd args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
AddChainRequest request = new()
{
@@ -45,10 +46,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task RemoveChainAsync(PrmApeChainRemove args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
RemoveChainRequest request = new()
{
@@ -70,10 +71,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task ListChainAsync(PrmApeChainList args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
ListChainsRequest request = new()
{
diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
index 14a3e2d..4e00a7d 100644
--- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs
@@ -12,20 +12,28 @@ using FrostFS.Session;
namespace FrostFS.SDK.ClientV2;
-internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider
+internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider
{
- readonly SessionProvider sessions = new(envCtx);
+ private SessionProvider? sessions;
public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx)
{
+ sessions ??= new(ClientContext);
+
+ if (ClientContext.SessionCache.Cache != null &&
+ ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey))
+ {
+ return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey];
+ }
+
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
internal async Task GetContainerAsync(PrmContainerGet args)
{
- GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context!);
+ GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context);
- var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
+ var response = await service.GetAsync(request, null, args.Context.Deadline, args.Context.CancellationToken);
Verifier.CheckResponse(response);
@@ -35,13 +43,13 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args)
{
var ctx = args.Context!;
- ctx.OwnerId ??= EnvironmentContext.Owner;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.OwnerId ??= ClientContext.Owner;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
if (ctx.OwnerId == null)
- throw new InvalidObjectException(nameof(ctx.OwnerId));
+ throw new ArgumentException(nameof(ctx.OwnerId));
var request = new ListRequest
{
@@ -74,11 +82,11 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
grpcContainer.Version ??= ctx.Version?.ToMessage();
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
if (grpcContainer.OwnerId == null)
- throw new InvalidObjectException(nameof(grpcContainer.OwnerId));
+ throw new ArgumentException(nameof(grpcContainer.OwnerId));
if (grpcContainer.Version == null)
- throw new InvalidObjectException(nameof(grpcContainer.Version));
+ throw new ArgumentException(nameof(grpcContainer.Version));
var request = new PutRequest
{
@@ -114,7 +122,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
{
var ctx = args.Context!;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new DeleteRequest
{
@@ -150,7 +158,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
{
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(ctx), "Key is null");
var request = new GetRequest
{
@@ -207,7 +215,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
await Task.Delay(waitParams.PollInterval).ConfigureAwait(false);
}
- catch (ResponseException ex)
+ catch (FrostFsResponseException ex)
{
if (DateTime.UtcNow >= deadLine)
throw new TimeoutException();
diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
index 91645fb..0241e31 100644
--- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -12,7 +13,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
{
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
- internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context)
+ internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientContext context)
: base(context)
{
this.netmapServiceClient = netmapServiceClient;
@@ -20,8 +21,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetNetworkSettingsAsync(CallContext ctx)
{
- if (EnvironmentContext.NetworkSettings != null)
- return EnvironmentContext.NetworkSettings;
+ if (ClientContext.NetworkSettings != null)
+ return ClientContext.NetworkSettings;
var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
@@ -38,7 +39,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
SetNetworksParam(param, settings);
}
- EnvironmentContext.NetworkSettings = settings;
+ ClientContext.NetworkSettings = settings;
return settings;
}
@@ -46,10 +47,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new LocalNodeInfoRequest
{
@@ -59,8 +60,6 @@ internal sealed class NetmapServiceProvider : ContextAccessor
request.AddMetaHeader(args.XHeaders);
request.Sign(ctx.Key);
-
-
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
@@ -70,10 +69,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetNetworkInfoAsync(CallContext ctx)
{
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(ctx), "Key is null");
var request = new NetworkInfoRequest();
@@ -91,10 +90,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor
internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new NetmapSnapshotRequest();
diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
index 6ab3362..bcea7c7 100644
--- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs
@@ -15,30 +15,32 @@ using Google.Protobuf;
namespace FrostFS.SDK.ClientV2;
-internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
+internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientContext clientCtx)
+ : ContextAccessor(clientCtx), ISessionProvider
{
- private readonly SessionProvider sessions;
- private ObjectService.ObjectServiceClient client;
-
- internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env)
- : base(env)
- {
- this.sessions = new(EnvironmentContext);
- this.client = client;
- }
+ private SessionProvider? sessions;
+ private readonly ObjectService.ObjectServiceClient client = client;
public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx)
{
+ sessions ??= new(ClientContext);
+
+ if (ClientContext.SessionCache.Cache != null &&
+ ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey))
+ {
+ return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey];
+ }
+
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
}
internal async Task GetObjectHeadAsync(PrmObjectHeadGet args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new HeadRequest
{
@@ -74,10 +76,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new GetRequest
{
@@ -108,10 +110,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
internal async Task DeleteObjectAsync(PrmObjectDelete args)
{
var ctx = args.Context!;
- ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
+ ctx.Key ??= ClientContext.Key?.ECDsaKey;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new DeleteRequest
{
@@ -145,7 +147,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var ctx = args.Context!;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var request = new SearchRequest
{
@@ -183,10 +185,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
throw new ArgumentNullException(nameof(args));
if (args.Header == null)
- throw new ArgumentException(nameof(args.Header));
+ throw new ArgumentNullException(nameof(args), "Header is null");
if (args.Payload == null)
- throw new ArgumentException(nameof(args.Payload));
+ throw new ArgumentNullException(nameof(args), "Payload is null");
if (args.ClientCut)
return await PutClientCutObject(args).ConfigureAwait(false);
@@ -206,7 +208,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var ctx = args.Context!;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx);
@@ -254,7 +256,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
if (args.MaxObjectSizeCache == 0)
{
- var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
+ var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx))
.ConfigureAwait(false);
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
@@ -306,7 +308,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds);
- _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context }).ConfigureAwait(false);
+ _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, args.Context)).ConfigureAwait(false);
var parentHeader = args.Header.GetHeader();
@@ -331,7 +333,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
{
var ctx = args.Context!;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
var payload = args.Payload!;
@@ -352,7 +354,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
}
else
{
- chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
+ chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
isRentBuffer = true;
}
@@ -409,7 +411,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
var header = args.Header!;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new ArgumentNullException(nameof(args), "Key is null");
header.OwnerId ??= ctx.OwnerId;
header.Version ??= ctx.Version;
diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
index 1dfe53f..73418d0 100644
--- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs
@@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
- internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context)
+ internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientContext context)
: base(context)
{
_sessionServiceClient = sessionServiceClient;
@@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
{
var ctx = args.Context!;
- ctx.OwnerId ??= EnvironmentContext.Owner;
+ ctx.OwnerId ??= ClientContext.Owner;
var request = new CreateRequest
{
diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
index 3fb7674..70fc9c9 100644
--- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs
@@ -1,6 +1,6 @@
namespace FrostFS.SDK.ClientV2;
-internal class ContextAccessor(EnvironmentContext context)
+internal class ContextAccessor(ClientContext context)
{
- protected EnvironmentContext EnvironmentContext { get; set; } = context;
+ protected ClientContext ClientContext { get; set; } = context;
}
diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
index 51fe337..e18de09 100644
--- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
+++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
@@ -7,14 +7,13 @@ internal interface ISessionProvider
ValueTask 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 GetOrCreateSession(ISessionToken args, CallContext ctx)
{
if (args.SessionToken is null)
{
- return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
+ return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx))
.ConfigureAwait(false);
}
diff --git a/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs
similarity index 62%
rename from src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs
rename to src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs
index d0596c9..c0a1db3 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs
@@ -2,16 +2,21 @@ using System;
using System.Buffers;
using System.Security.Cryptography;
+using FrostFS.SDK.Cryptography;
+
using Grpc.Net.Client;
namespace FrostFS.SDK.ClientV2;
-public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
+public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
{
private ArrayPool? _arrayPool;
+ private string? sessionKey;
internal FrostFsOwner? Owner { get; } = owner;
+ internal string? Address { get; } = channel.Target;
+
internal GrpcChannel Channel { get; private set; } = channel;
internal FrostFsVersion Version { get; } = version;
@@ -22,6 +27,21 @@ public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner?
internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null;
+ internal SessionCache SessionCache { get; set; }
+
+ internal string? SessionCacheKey
+ {
+ get
+ {
+ if (sessionKey == null && Key != null && Address != null)
+ {
+ sessionKey = Pool.FormCacheKey(Address, Key.ECDsaKey.PrivateKey().ToString());
+ }
+
+ return sessionKey;
+ }
+ }
+
///
/// Custom pool is used for predefined sizes of buffers like grpc chunk
///
diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs
index b1ad4f7..7df2343 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs
@@ -17,13 +17,13 @@ public sealed class ObjectReader(AsyncServerStreamingCall call) : I
internal async Task ReadHeader()
{
if (!await Call.ResponseStream.MoveNext().ConfigureAwait(false))
- throw new InvalidOperationException("unexpected end of stream");
+ throw new FrostFsStreamException("unexpected end of stream");
var response = Call.ResponseStream.Current;
Verifier.CheckResponse(response);
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init)
- throw new InvalidOperationException("unexpected message type");
+ throw new FrostFsStreamException("unexpected message type");
return new Object.Object
{
@@ -41,7 +41,7 @@ public sealed class ObjectReader(AsyncServerStreamingCall call) : I
Verifier.CheckResponse(response);
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk)
- throw new InvalidOperationException("unexpected message type");
+ throw new FrostFsStreamException("unexpected message type");
return response.Body.Chunk.Memory;
}
diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
index f4d659c..91df2ad 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs
@@ -59,7 +59,7 @@ internal static class ObjectTools
return;
if (ctx.Key == null)
- throw new InvalidObjectException(nameof(ctx.Key));
+ throw new FrostFsInvalidObjectException(nameof(ctx.Key));
grpcHeader.Split = new Header.Types.Split
{
diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs
index 2896209..c87ef0a 100644
--- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs
+++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs
@@ -122,7 +122,7 @@ public static class Verifier
var status = resp.MetaHeader.Status.ToModel();
if (status != null && !status.IsSuccess)
- throw new ResponseException(status);
+ throw new FrostFsResponseException(status);
}
///
@@ -137,6 +137,6 @@ public static class Verifier
}
if (!request.Verify())
- throw new FormatException($"invalid response, type={request.GetType()}");
+ throw new FrostFsResponseException($"invalid response, type={request.GetType()}");
}
}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.Tests/CallbackInterceptor.cs b/src/FrostFS.SDK.Tests/CallbackInterceptor.cs
new file mode 100644
index 0000000..c4eb726
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/CallbackInterceptor.cs
@@ -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? callback = null) : Interceptor
+{
+ public override AsyncUnaryCall AsyncUnaryCall(
+ TRequest request,
+ ClientInterceptorContext context,
+ AsyncUnaryCallContinuation continuation)
+ {
+ var call = continuation(request, context);
+
+ return new AsyncUnaryCall(
+ HandleUnaryResponse(call),
+ call.ResponseHeadersAsync,
+ call.GetStatus,
+ call.GetTrailers,
+ call.Dispose);
+ }
+
+ private async Task HandleUnaryResponse(AsyncUnaryCall call)
+ {
+ var response = await call;
+
+ callback?.Invoke($"elapsed");
+
+ return response;
+ }
+}
diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs
deleted file mode 100644
index 9f5d824..0000000
--- a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs
+++ /dev/null
@@ -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 AsyncUnaryCall(
- TRequest request,
- ClientInterceptorContext context,
- AsyncUnaryCallContinuation continuation)
- {
- ArgumentNullException.ThrowIfNull(continuation);
-
- using var call = continuation(request, context);
-
- return new AsyncUnaryCall(
- HandleUnaryResponse(call),
- call.ResponseHeadersAsync,
- call.GetStatus,
- call.GetTrailers,
- call.Dispose);
- }
-
- private static async Task HandleUnaryResponse(AsyncUnaryCall 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;
- }
-}
diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs
index f09cafb..99fa2b2 100644
--- a/src/FrostFS.SDK.Tests/NetworkTest.cs
+++ b/src/FrostFS.SDK.Tests/NetworkTest.cs
@@ -28,19 +28,16 @@ public class NetworkTest : NetworkTestsBase
Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
- var param = new PrmNetworkSettings();
-
- if (useContext)
- {
- param.Context = new CallContext
+ var param = useContext ?
+ new PrmNetworkSettings(new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Version
- };
- }
+ })
+ : new PrmNetworkSettings();
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
@@ -116,12 +113,11 @@ public class NetworkTest : NetworkTestsBase
Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body };
- var param = new PrmNetmapSnapshot();
+ PrmNetmapSnapshot param;
if (useContext)
{
- param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new CallContext
+ var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -129,6 +125,14 @@ public class NetworkTest : NetworkTestsBase
Key = ECDsaKey,
Version = Version
};
+
+ param = new(ctx);
+ param.XHeaders.Add("headerKey1", "headerValue1");
+
+ }
+ else
+ {
+ param = new();
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
@@ -208,12 +212,11 @@ public class NetworkTest : NetworkTestsBase
Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body };
- var param = new PrmNodeInfo();
+ PrmNodeInfo param;
if (useContext)
{
- param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new CallContext
+ var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -221,6 +224,14 @@ public class NetworkTest : NetworkTestsBase
Key = ECDsaKey,
Version = Version
};
+
+ param = new(ctx);
+ param.XHeaders.Add("headerKey1", "headerValue1");
+
+ }
+ else
+ {
+ param = new();
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs
index 07fa8e9..85b42ca 100644
--- a/src/FrostFS.SDK.Tests/ObjectTest.cs
+++ b/src/FrostFS.SDK.Tests/ObjectTest.cs
@@ -32,7 +32,7 @@ public class ObjectTest : ObjectTestsBase
var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx);
- var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx });
+ var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId, ctx));
Assert.NotNull(result);
@@ -50,7 +50,7 @@ public class ObjectTest : ObjectTestsBase
[Fact]
public async void PutObjectTest()
{
- Mocker.ResultObjectIds.Add(SHA256.HashData([]));
+ Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
Random rnd = new();
var bytes = new byte[1024];
@@ -107,7 +107,7 @@ public class ObjectTest : ObjectTestsBase
rnd.NextBytes(objIds.ElementAt(2));
foreach (var objId in objIds)
- Mocker.ResultObjectIds.Add(objId);
+ Mocker.ResultObjectIds!.Add(objId);
var result = await GetClient().PutObjectAsync(param);
diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs
index 87bf106..01320b5 100644
--- a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs
+++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs
@@ -23,16 +23,6 @@ public class PoolSmokeTests : SmokeTestsBase
Key = keyString.LoadWif(),
NodeParams = [new(1, this.url, 100.0f)],
- DialOptions = [new()
- {
- Authority = "",
- Block = false,
- DisableHealthCheck = false,
- DisableRetry = false,
- ReturnLastError = true,
- Timeout = 30_000_000
- }
- ],
ClientBuilder = null,
GracefulCloseOnSwitchTimeout = 30_000_000,
Logger = null
@@ -85,6 +75,44 @@ public class PoolSmokeTests : SmokeTestsBase
Assert.Equal(9, result.Attributes.Count);
}
+ [Fact]
+ public async void NodeInfoStatisticsTwoNodesTest()
+ {
+ var options = new InitParameters
+ {
+ Key = keyString.LoadWif(),
+ NodeParams = [
+ new(1, this.url, 100.0f),
+ new(2, this.url.Replace('0', '1'), 100.0f)
+ ],
+ ClientBuilder = null,
+ GracefulCloseOnSwitchTimeout = 30_000_000,
+ Logger = null
+ };
+
+ using var pool = new Pool(options);
+
+ var callbackText = string.Empty;
+
+ var ctx = new CallContext
+ {
+ Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"
+ };
+
+ var error = await pool.Dial(ctx).ConfigureAwait(true);
+
+ Assert.Null(error);
+
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
+
+ var result = await client.GetNodeInfoAsync();
+
+ var statistics = pool.Statistic();
+
+ Assert.False(string.IsNullOrEmpty(callbackText));
+ Assert.Contains(" took ", callbackText, StringComparison.Ordinal);
+ }
+
[Fact]
public async void NodeInfoStatisticsTest()
{
@@ -308,31 +336,29 @@ public class PoolSmokeTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
- new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
- {
- Context = ctx
- };
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
var createdContainer = await pool.CreateContainerAsync(createContainerParam);
- var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
+ var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer));
Assert.NotNull(container);
Assert.True(callbackInvoked);
var bytes = GetRandomBytes(objectSize);
- var param = new PrmObjectPut
+ var ctx1 = new CallContext
+ {
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ };
+
+ var param = new PrmObjectPut(ctx1)
{
Header = new FrostFsObjectHeader(
containerId: createdContainer,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
- ClientCut = false,
- Context = new CallContext
- {
- Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
- }
+ ClientCut = false
};
var objectId = await pool.PutObjectAsync(param);
@@ -400,19 +426,16 @@ public class PoolSmokeTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
- new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
- {
- Context = ctx
- };
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
var container = await pool.CreateContainerAsync(createContainerParam);
- var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
+ var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container, ctx));
Assert.NotNull(containerInfo);
var bytes = GetRandomBytes(objectSize);
- var param = new PrmObjectPut
+ var param = new PrmObjectPut(new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) })
{
Header = new FrostFsObjectHeader(
containerId: container,
@@ -420,10 +443,6 @@ public class PoolSmokeTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
- Context = new CallContext
- {
- Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
- },
SessionToken = token
};
@@ -496,10 +515,11 @@ public class PoolSmokeTests : SmokeTestsBase
var ctx = new CallContext
{
Timeout = TimeSpan.FromSeconds(10),
- Interceptors = new([new MetricsInterceptor()])
};
- var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
+ ctx.Interceptors.Add(new CallbackInterceptor());
+
+ var container = await pool.GetContainerAsync(new PrmContainerGet(containerId, ctx));
Assert.NotNull(container);
@@ -520,7 +540,7 @@ public class PoolSmokeTests : SmokeTestsBase
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
bool hasObject = false;
- await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
+ await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter)))
{
hasObject = true;
@@ -551,21 +571,10 @@ public class PoolSmokeTests : SmokeTestsBase
await Cleanup(pool);
- var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
-
- IAsyncEnumerator? enumerator = null;
- do
+ await foreach (var cid in pool.ListContainersAsync())
{
- if (deadline <= DateTime.UtcNow)
- {
- Assert.Fail("Containers exist");
- break;
- }
-
- enumerator = pool.ListContainersAsync().GetAsyncEnumerator();
- await Task.Delay(500);
+ Assert.Fail($"Container {cid.GetValue()} exist");
}
- while (await enumerator!.MoveNextAsync());
}
private static byte[] GetRandomBytes(int size)
diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs
index e343c82..8009f5c 100644
--- a/src/FrostFS.SDK.Tests/SessionTests.cs
+++ b/src/FrostFS.SDK.Tests/SessionTests.cs
@@ -14,12 +14,11 @@ public class SessionTest : SessionTestsBase
public async void CreateSessionTest(bool useContext)
{
var exp = 100u;
- var param = new PrmSessionCreate(exp);
+ PrmSessionCreate param;
if (useContext)
{
- param.XHeaders.Add("headerKey1", "headerValue1");
- param.Context = new CallContext
+ var ctx = new CallContext
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
@@ -27,6 +26,14 @@ public class SessionTest : SessionTestsBase
Key = ECDsaKey,
Version = Mocker.Version
};
+
+ param = new PrmSessionCreate(exp, ctx);
+
+ param.XHeaders.Add("headerKey1", "headerValue1");
+ }
+ else
+ {
+ param = new PrmSessionCreate(exp);
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs
index 54868d6..426aa9b 100644
--- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs
+++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs
@@ -26,7 +26,7 @@ public class SmokeClientTests : SmokeTestsBase
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
: FrostFSClient.GetInstance(GetOptions(this.url));
- PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
+ PrmBalance? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetBalanceAsync(prm);
}
@@ -38,7 +38,7 @@ public class SmokeClientTests : SmokeTestsBase
{
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
- PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
+ PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetNetmapSnapshotAsync(prm);
Assert.True(result.Epoch > 0);
@@ -59,9 +59,11 @@ public class SmokeClientTests : SmokeTestsBase
[InlineData(true)]
public async void NodeInfoTest(bool isSingleOnwerClient)
{
- using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
+ using var client = isSingleOnwerClient
+ ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
+ : FrostFSClient.GetInstance(GetOptions(this.url));
- PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
+ PrmNodeInfo? prm = isSingleOnwerClient ? default : new(Ctx);
var result = await client.GetNodeInfoAsync(prm);
@@ -93,7 +95,7 @@ public class SmokeClientTests : SmokeTestsBase
{
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
- PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx };
+ PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100, Ctx);
var token = await client.CreateSessionAsync(prm);
@@ -262,31 +264,27 @@ public class SmokeClientTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
- new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
- {
- Context = ctx
- };
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx);
var createdContainer = await client.CreateContainerAsync(createContainerParam);
- var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
+ var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx));
Assert.NotNull(container);
Assert.True(callbackInvoked);
var bytes = GetRandomBytes(objectSize);
- var param = new PrmObjectPut
+ var param = new PrmObjectPut(new CallContext
+ {
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ })
{
Header = new FrostFsObjectHeader(
containerId: createdContainer,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
- ClientCut = false,
- Context = new CallContext
- {
- Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
- }
+ ClientCut = false
};
var objectId = await client.PutObjectAsync(param);
@@ -348,19 +346,19 @@ public class SmokeClientTests : SmokeTestsBase
};
var createContainerParam = new PrmContainerCreate(
- new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
- {
- Context = ctx
- };
+ new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx);
var container = await client.CreateContainerAsync(createContainerParam);
- var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
+ var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container, ctx));
Assert.NotNull(containerInfo);
var bytes = GetRandomBytes(objectSize);
- var param = new PrmObjectPut
+ var param = new PrmObjectPut(new CallContext
+ {
+ Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
+ })
{
Header = new FrostFsObjectHeader(
containerId: container,
@@ -368,10 +366,6 @@ public class SmokeClientTests : SmokeTestsBase
[new FrostFsAttributePair("fileName", "test")]),
Payload = new MemoryStream(bytes),
ClientCut = false,
- Context = new CallContext
- {
- Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
- },
SessionToken = token
};
@@ -437,11 +431,12 @@ public class SmokeClientTests : SmokeTestsBase
var ctx = new CallContext
{
- Timeout = TimeSpan.FromSeconds(10),
- Interceptors = new([new MetricsInterceptor()])
+ Timeout = TimeSpan.FromSeconds(10)
};
- var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
+ ctx.Interceptors.Add(new CallbackInterceptor());
+
+ var container = await client.GetContainerAsync(new PrmContainerGet(containerId, ctx));
Assert.NotNull(container);
@@ -462,7 +457,7 @@ public class SmokeClientTests : SmokeTestsBase
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
bool hasObject = false;
- await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
+ await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter)))
{
hasObject = true;
@@ -510,6 +505,38 @@ public class SmokeClientTests : SmokeTestsBase
while (await enumerator!.MoveNextAsync());
}
+ [Fact]
+ public async void NodeInfoCallbackAndInterceptorTest()
+ {
+ using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
+
+ bool callbackInvoked = false;
+ bool intercepterInvoked = false;
+
+ var ctx = new CallContext
+ {
+ Callback = new((CallStatistics cs) =>
+ {
+ callbackInvoked = true;
+ Assert.True(cs.ElapsedMicroSeconds > 0);
+ })
+ };
+
+ ctx.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true));
+
+ var result = await client.GetNodeInfoAsync(new PrmNodeInfo(ctx));
+
+ Assert.True(callbackInvoked);
+ Assert.True(intercepterInvoked);
+
+ Assert.Equal(2, result.Version.Major);
+ Assert.Equal(13, result.Version.Minor);
+ Assert.Equal(NodeState.Online, result.State);
+ Assert.Equal(33, result.PublicKey.Length);
+ Assert.Single(result.Addresses);
+ Assert.Equal(9, result.Attributes.Count);
+ }
+
private static byte[] GetRandomBytes(int size)
{
Random rnd = new();
--
2.45.2
From bff8d6786767a8ece329676427a26004abb4685c Mon Sep 17 00:00:00 2001
From: Pavel Gross
Date: Fri, 1 Nov 2024 10:41:17 +0300
Subject: [PATCH 3/3] [#24] Client: Implement pool part2
Unicode fix
Signed-off-by: Pavel Gross
---
src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 39 ++++++++++++++-------------
1 file changed, 20 insertions(+), 19 deletions(-)
diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
index 2602746..3e20634 100644
--- a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
+++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -304,7 +305,7 @@ public partial class Pool : IFrostFSClient
);
}
- private ClientWrapper Сonnection()
+ private ClientWrapper Connection()
{
foreach (var pool in InnerPools!)
{
@@ -518,7 +519,7 @@ public partial class Pool : IFrostFSClient
public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
{
- var client = Сonnection();
+ var client = Connection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
@@ -528,7 +529,7 @@ public partial class Pool : IFrostFSClient
public async Task GetNodeInfoAsync(PrmNodeInfo? args = null)
{
- var client = Сonnection();
+ var client = Connection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
@@ -538,7 +539,7 @@ public partial class Pool : IFrostFSClient
public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
{
- var client = Сonnection();
+ var client = Connection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
@@ -553,7 +554,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -567,7 +568,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -581,7 +582,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -595,7 +596,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -609,7 +610,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -618,7 +619,7 @@ public partial class Pool : IFrostFSClient
public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null)
{
- var client = Сonnection();
+ var client = Connection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
@@ -633,7 +634,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -647,7 +648,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -661,7 +662,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -675,7 +676,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -689,7 +690,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -703,7 +704,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -717,7 +718,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -731,7 +732,7 @@ public partial class Pool : IFrostFSClient
throw new ArgumentNullException(nameof(args));
}
- var client = Сonnection();
+ var client = Connection();
args.Context.PoolErrorHandler = client.HandleError;
@@ -740,7 +741,7 @@ public partial class Pool : IFrostFSClient
public async Task GetBalanceAsync(PrmBalance? args)
{
- var client = Сonnection();
+ var client = Connection();
args ??= new();
args.Context.PoolErrorHandler = client.HandleError;
--
2.45.2