Compare commits

...

7 commits

Author SHA1 Message Date
cf1fae8d2c span and memory 2024-11-12 04:00:26 +03:00
423b5a35b0 net standard 2.1 2024-11-12 00:52:48 +03:00
d4c88467d8 V2 removing 2024-11-12 00:35:07 +03:00
2a71ea3ff9 patch 2024-11-08 07:17:30 +03:00
c107a219b5 unicode fix 2024-11-01 10:40:07 +03:00
7125ecae64 pool part 2 2024-11-01 10:12:34 +03:00
c36a707cb5 temp 2024-10-21 10:36:57 +03:00
243 changed files with 4973 additions and 2418 deletions

View file

@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Client", "src\FrostFS.SDK.Client\FrostFS.SDK.Client.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Protos", "src\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}"
EndProject

View file

@ -0,0 +1,22 @@
using Microsoft.Extensions.Caching.Memory;
namespace FrostFS.SDK.Client;
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;
}

View file

@ -0,0 +1,14 @@
using System.Security.Cryptography;
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
namespace FrostFS.SDK.Client;
public class ClientKey(ECDsa key)
{
internal ECDsa ECDsaKey { get; } = key;
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
}

View file

@ -1,6 +1,6 @@
using System;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public class FrostFsException : Exception
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
@ -22,15 +22,15 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
<ProjectReference Include="..\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj" />
<ProjectReference Include="..\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj" />
</ItemGroup>
</Project>

View file

@ -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.Client.Interfaces;
using FrostFS.SDK.Client.Services;
using FrostFS.SDK.Cryptography;
using FrostFS.Session;
@ -19,7 +21,7 @@ using Grpc.Net.Client;
using Microsoft.Extensions.Options;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public class FrostFSClient : IFrostFSClient
{
@ -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 ClientContext ClientCtx { get; set; }
public static IFrostFSClient GetInstance(IOptions<ClientSettings> 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 ClientContext(
client: this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@ -104,13 +108,13 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new ClientEnvironment(
ClientCtx = new ClientContext(
this,
key: null,
owner: null,
@ -123,7 +127,7 @@ public class FrostFSClient : IFrostFSClient
private FrostFSClient(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
clientSettings.Validate();
@ -131,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new ClientEnvironment(
ClientCtx = new ClientContext(
this,
key: ecdsaKey,
owner: FrostFsOwner.FromKey(ecdsaKey),
@ -142,6 +146,19 @@ public class FrostFSClient : IFrostFSClient
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
}
internal FrostFSClient(WrapperPrm prm, SessionCache cache)
{
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))
{
SessionCache = cache
};
}
public void Dispose()
{
Dispose(true);
@ -158,7 +175,7 @@ public class FrostFSClient : IFrostFSClient
}
#region ApeManagerImplementation
public Task<byte[]> AddChainAsync(PrmApeChainAdd args)
public Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args)
{
if (args is null)
{
@ -268,6 +285,25 @@ public class FrostFSClient : IFrostFSClient
return service.GetObjectAsync(args);
}
public Task<RangeReader> GetRangeAsync(PrmRangeGet args)
{
if (args is null)
throw new ArgumentNullException(nameof(args));
var service = GetObjectService(args);
return service.GetRangeAsync(args);
}
public Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args)
{
if (args is null)
throw new ArgumentNullException(nameof(args));
var service = GetObjectService(args);
return service.GetRangeHashAsync(args);
}
public Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
{
if (args is null)
@ -286,6 +322,17 @@ public class FrostFSClient : IFrostFSClient
return service.PutSingleObjectAsync(args);
}
public Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
var service = GetObjectService(args);
return service.PatchObjectAsync(args);
}
public Task DeleteObjectAsync(PrmObjectDelete args)
{
if (args is null)
@ -305,16 +352,16 @@ public class FrostFSClient : IFrostFSClient
}
#endregion
#region SessionImplementation
#region Session Implementation
public async Task<FrostFsSessionToken> 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<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args)
@ -327,8 +374,18 @@ public class FrostFSClient : IFrostFSClient
}
#endregion
#region Accounting Implementation
public async Task<Accounting.Decimal> 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,12 +394,12 @@ 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 };
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);
@ -354,18 +411,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 Context();
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;
@ -380,24 +435,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)
@ -405,7 +459,7 @@ public class FrostFSClient : IFrostFSClient
if (callInvoker == null)
callInvoker = ClientCtx.Channel.Intercept(interceptor);
else
callInvoker.Intercept(interceptor);
callInvoker = callInvoker.Intercept(interceptor);
return callInvoker;
}
@ -413,7 +467,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));
@ -423,7 +477,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));
@ -433,7 +487,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));
@ -441,9 +495,19 @@ public class FrostFSClient : IFrostFSClient
return new ApeManagerServiceProvider(client, ClientCtx);
}
private AccountingServiceProvider GetAccouningService(IContext ctx)
{
var callInvoker = SetupClientContext(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);
var callInvoker = SetupClientContext(ctx);
var client = ContainerServiceClient ?? (callInvoker != null
? new ContainerService.ContainerServiceClient(callInvoker)
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
@ -453,7 +517,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));
@ -461,6 +525,16 @@ public class FrostFSClient : IFrostFSClient
return new ObjectServiceProvider(client, ClientCtx);
}
private AccountingServiceProvider GetAccountService(IContext ctx)
{
var callInvoker = SetupClientContext(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 +554,24 @@ public class FrostFSClient : IFrostFSClient
throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}");
}
}
public async Task<string?> Dial(CallContext ctx)
{
var prm = new PrmBalance(ctx);
var service = GetAccouningService(prm);
_ = await service.GetBallance(prm).ConfigureAwait(false);
return null;
}
public bool RestartIfUnhealthy(CallContext ctx)
{
throw new NotImplementedException();
}
public void Close()
{
Dispose();
}
}

View file

@ -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 = "<Pending>", Scope = "member", Target = "~M:FrostFS.SDK.Client.Sampler.Next~System.Int32")]

View file

@ -0,0 +1,68 @@
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace FrostFS.SDK.Client;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
Justification = "parameters are provided by GRPC infrastructure")]
public class ErrorInterceptor(Action<Exception> handler) : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var call = continuation(request, context);
return new AsyncUnaryCall<TResponse>(
HandleUnaryResponse(call),
call.ResponseHeadersAsync,
call.GetStatus,
call.GetTrailers,
call.Dispose);
}
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(
ClientInterceptorContext<TRequest, TResponse> context,
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
{
var call = continuation(context);
return new AsyncClientStreamingCall<TRequest, TResponse>(
call.RequestStream,
HandleStreamResponse(call),
call.ResponseHeadersAsync,
call.GetStatus,
call.GetTrailers,
call.Dispose);
}
private async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> call)
{
try
{
return await call;
}
catch (Exception ex)
{
handler(ex);
throw;
}
}
private async Task<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> call)
{
try
{
return await call;
}
catch (Exception ex)
{
handler(ex);
throw;
}
}
}

View file

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

View file

@ -3,7 +3,8 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Frostfs.V2.Ape;
namespace FrostFS.SDK.ClientV2.Interfaces;
namespace FrostFS.SDK.Client.Interfaces;
public interface IFrostFSClient : IDisposable
{
@ -19,8 +20,8 @@ public interface IFrostFSClient : IDisposable
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args);
#endregion
#region ApeMAnager
Task<byte[]> AddChainAsync(PrmApeChainAdd args);
#region ApeManager
Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args);
Task RemoveChainAsync(PrmApeChainRemove args);
@ -42,16 +43,30 @@ public interface IFrostFSClient : IDisposable
Task<FrostFsObject> GetObjectAsync(PrmObjectGet args);
Task<RangeReader> GetRangeAsync(PrmRangeGet args);
Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args);
Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args);
Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args);
Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args);
Task DeleteObjectAsync(PrmObjectDelete args);
IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args);
#endregion
#region Tools
FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx);
#region Account
Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args = null);
#endregion
#region Tools
FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx);
#endregion
public Task<string?> Dial(CallContext ctx);
public void Close();
}

View file

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

View file

@ -3,7 +3,7 @@ using System.Linq;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ContainerMapper
{

View file

@ -7,7 +7,7 @@ using Google.Protobuf;
using Microsoft.Extensions.Caching.Memory;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ContainerIdMapper
{
@ -24,16 +24,25 @@ 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!;
}
public static FrostFsContainerId ToModel(this ContainerID message)
{
if (message is null)
{
throw new ArgumentNullException(nameof(message));
}
return new FrostFsContainerId(Base58.Encode(message.Value.Span));
}
}

View file

@ -2,7 +2,7 @@ using System;
using FrostFS.Session;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class MetaHeaderMapper
{

View file

@ -3,7 +3,7 @@ using System.Linq;
using FrostFS.Netmap;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public static class NetmapMapper
{

View file

@ -2,9 +2,9 @@ using System;
using System.Linq;
using FrostFS.Netmap;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client.Mappers.GRPC;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public static class NodeInfoMapper
{
@ -39,7 +39,7 @@ public static class NodeInfoMapper
state: state,
addresses: [.. nodeInfo.Addresses],
attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value),
publicKey: nodeInfo.PublicKey.ToByteArray()
publicKey: nodeInfo.PublicKey.Memory
);
}
}

View file

@ -3,7 +3,7 @@ using System.Linq;
using FrostFS.Netmap;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public static class PlacementPolicyMapper
{

View file

@ -2,7 +2,7 @@ using System;
using FrostFS.Netmap;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public static class ReplicaMapper
{

View file

@ -1,4 +1,4 @@
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
internal static class ObjectMapper
{
@ -6,7 +6,7 @@ internal static class ObjectMapper
{
return new FrostFsObject(obj.Header.ToModel())
{
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray())
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.Span)
};
}
}

View file

@ -2,7 +2,7 @@ using System;
using FrostFS.Object;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ObjectAttributeMapper
{

View file

@ -2,7 +2,7 @@ using System;
using FrostFS.Object;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ObjectFilterMapper
{

View file

@ -5,7 +5,7 @@ using System.Linq;
using FrostFS.Object;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ObjectHeaderMapper
{
@ -40,7 +40,7 @@ public static class ObjectHeaderMapper
}
var model = new FrostFsObjectHeader(
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())),
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)),
objTypeName,
header.Attributes.Select(attribute => attribute.ToModel()).ToArray(),
split,

View file

@ -4,7 +4,7 @@ using FrostFS.Refs;
using Google.Protobuf;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class ObjectIdMapper
{
@ -28,6 +28,6 @@ public static class ObjectIdMapper
throw new ArgumentNullException(nameof(objectId));
}
return FrostFsObjectId.FromHash(objectId.Value.ToByteArray());
return FrostFsObjectId.FromHash(objectId.Value.Span);
}
}

View file

@ -7,7 +7,7 @@ using Google.Protobuf;
using Microsoft.Extensions.Caching.Memory;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class OwnerIdMapper
{
@ -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()));
model = new FrostFsOwner(Base58.Encode(message.Value.Span));
Cache.Owners.Set(message, model, _oneHourExpiration);
Caches.Owners.Set(message, model, _oneHourExpiration);
}
return model!;

View file

@ -2,7 +2,7 @@ using System;
using Google.Protobuf;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public static class SessionMapper
{

View file

@ -2,7 +2,7 @@ using System;
using Google.Protobuf;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class SignatureMapper
{

View file

@ -1,6 +1,6 @@
using System;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class StatusMapper
{

View file

@ -3,7 +3,7 @@ using System.Threading;
using FrostFS.Refs;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
namespace FrostFS.SDK.Client.Mappers.GRPC;
public static class VersionMapper
{

View file

@ -0,0 +1,62 @@
using System;
using Frostfs.V2.Ape;
namespace FrostFS.SDK.Client;
public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable<FrostFsChainTarget>
{
private ChainTarget? chainTarget;
public FrostFsTargetType Type { get; } = type;
public string Name { get; } = name;
internal ChainTarget GetChainTarget()
{
return chainTarget ??= new ChainTarget
{
Type = GetTargetType(Type),
Name = Name
};
}
private static TargetType GetTargetType(FrostFsTargetType type)
{
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)),
};
}
public override readonly bool Equals(object obj)
{
var target = (FrostFsChainTarget)obj;
return Equals(target);
}
public override readonly int GetHashCode()
{
return $"{Name}{Type}".GetHashCode(StringComparison.InvariantCulture);
}
public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
{
return left.Equals(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);
}
}

View file

@ -0,0 +1,41 @@
using Google.Protobuf;
namespace FrostFS.SDK.Client;
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
{
private ByteString? grpcRaw;
public byte[] Raw { get; } = raw;
internal ByteString GetRaw()
{
return grpcRaw ??= ByteString.CopyFrom(Raw);
}
public override readonly bool Equals(object obj)
{
var chain = (FrostFsChain)obj;
return Equals(chain);
}
public override readonly int GetHashCode()
{
return Raw.GetHashCode();
}
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;
}
}

View file

@ -0,0 +1,10 @@
namespace FrostFS.SDK.Client;
public enum FrostFsTargetType
{
Undefined = 0,
Namespace,
Container,
User,
Group
}

View file

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

View file

@ -1,6 +1,6 @@
using FrostFS.Refs;
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client;
using FrostFS.SDK.Client.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK;
@ -27,11 +27,11 @@ public class FrostFsContainerId
if (containerID != null)
{
this.modelId = Base58.Encode(containerID.Value.ToByteArray());
this.modelId = Base58.Encode(containerID.Value.Span);
return this.modelId;
}
throw new InvalidObjectException();
throw new FrostFsInvalidObjectException();
}
internal ContainerID ContainerID
@ -47,7 +47,7 @@ public class FrostFsContainerId
return this.containerID;
}
throw new InvalidObjectException();
throw new FrostFsInvalidObjectException();
}
}

View file

@ -2,8 +2,8 @@ using System;
using System.Collections.ObjectModel;
using System.Linq;
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client;
using FrostFS.SDK.Client.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
@ -88,7 +88,7 @@ public class FrostFsContainerInfo
{
if (PlacementPolicy == null)
{
throw new InvalidObjectException("PlacementPolicy is null");
throw new ArgumentNullException("PlacementPolicy is null");
}
this.container = new Container.Container()

View file

@ -16,6 +16,6 @@ public class CheckSum
public override string ToString()
{
return text ??= BitConverter.ToString(hash).Replace("-", "");
return text ??= BitConverter.ToString(hash).Replace("-", "", StringComparison.InvariantCulture);
}
}

View file

@ -3,7 +3,7 @@ using System;
using System.Linq;
using FrostFS.Netmap;
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.Client;
namespace FrostFS.SDK;

View file

@ -27,7 +27,7 @@ public struct FrostFsReplica : IEquatable<FrostFsReplica>
public override readonly int GetHashCode()
{
return Count + Selector.GetHashCode();
return Count + Selector.GetHashCode(StringComparison.InvariantCulture);
}
public static bool operator ==(FrostFsReplica left, FrostFsReplica right)

View file

@ -1,5 +1,5 @@
using FrostFS.Refs;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client.Mappers.GRPC;
namespace FrostFS.SDK;

View file

@ -0,0 +1,48 @@
using FrostFS.Refs;
using FrostFS.SDK.Client.Mappers.GRPC;
namespace FrostFS.SDK;
public class FrostFsAddress
{
private FrostFsObjectId? frostFsObjectId;
private FrostFsContainerId? frostFsContainerId;
private ObjectID? objectId;
private ContainerID? containerId;
public FrostFsAddress(FrostFsContainerId frostFsContainerId, FrostFsObjectId frostFsObjectId)
{
FrostFsObjectId = frostFsObjectId ?? throw new System.ArgumentNullException(nameof(frostFsObjectId));
FrostFsContainerId = frostFsContainerId ?? throw new System.ArgumentNullException(nameof(frostFsContainerId));
}
internal FrostFsAddress(ObjectID objectId, ContainerID containerId)
{
ObjectId = objectId ?? throw new System.ArgumentNullException(nameof(objectId));
ContainerId = containerId ?? throw new System.ArgumentNullException(nameof(containerId));
}
public FrostFsObjectId FrostFsObjectId
{
get => frostFsObjectId ??= objectId!.ToModel();
set => frostFsObjectId = value;
}
public FrostFsContainerId FrostFsContainerId
{
get => frostFsContainerId ??= containerId!.ToModel();
set => frostFsContainerId = value;
}
public ObjectID ObjectId
{
get => objectId ??= frostFsObjectId!.ToMessage();
set => objectId = value;
}
public ContainerID ContainerId
{
get => containerId ??= frostFsContainerId!.ToMessage();
set => containerId = value;
}
}

View file

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

View file

@ -4,7 +4,7 @@ using System.Linq;
using FrostFS.Object;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client.Mappers.GRPC;
namespace FrostFS.SDK;

View file

@ -8,13 +8,8 @@ public class FrostFsObjectId(string id)
{
public string Value { get; } = id;
public static FrostFsObjectId FromHash(byte[] hash)
public static FrostFsObjectId FromHash(ReadOnlySpan<byte> hash)
{
if (hash is null)
{
throw new ArgumentNullException(nameof(hash));
}
if (hash.Length != Constants.Sha256HashLength)
throw new FormatException("ObjectID must be a sha256 hash.");

View file

@ -1,7 +1,7 @@
using System.Security.Cryptography;
using FrostFS.Refs;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Client.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK;

View file

@ -0,0 +1,29 @@
using System;
namespace FrostFS.SDK;
public readonly struct FrostFsRange(ulong offset, ulong length) : System.IEquatable<FrostFsRange>
{
public ulong Offset { get; } = offset;
public ulong Length { get; } = length;
public override readonly bool Equals(object obj) => this == (FrostFsRange)obj;
public override readonly int GetHashCode() => $"{Offset}{Length}".GetHashCode(StringComparison.InvariantCulture);
public static bool operator ==(FrostFsRange left, FrostFsRange right)
{
return left.Equals(right);
}
public static bool operator !=(FrostFsRange left, FrostFsRange right)
{
return !(left == right);
}
public readonly bool Equals(FrostFsRange other)
{
return this == other;
}
}

View file

@ -6,5 +6,5 @@ namespace FrostFS.SDK;
public interface IObjectReader : IDisposable
{
Task<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
ValueTask<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,9 @@
using System;
namespace FrostFS.SDK;
public class FrostFsSessionToken(byte[] token, Guid id)
{
public Guid Id { get; private set; } = id;
public byte[] Token { get; private set; } = token;
}

View file

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

View file

@ -1,6 +1,6 @@
using System.Security.Cryptography;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public class Credentials(ECDsa key, FrostFsOwner ownerId)
{

View file

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

View file

@ -1,4 +1,4 @@
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public interface ISessionToken
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,24 @@
using System.IO;
namespace FrostFS.SDK.Client;
public sealed class PrmObjectPatch(FrostFsAddress address, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsAddress Address { get; } = address;
public FrostFsRange Range { get; set; }
/// <summary>
/// A stream with source data
/// </summary>
public Stream? Payload { get; set; }
public FrostFsAttributePair[]? NewAttributes { get; set; }
public bool ReplaceAttributes { get; set; }
public int MaxPayloadPatchChunkLength { get; set; }
/// <inheritdoc />
public FrostFsSessionToken? SessionToken { get; set; }
}

View file

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

View file

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

View file

@ -0,0 +1,20 @@
namespace FrostFS.SDK.Client;
public sealed class PrmRangeGet(
FrostFsContainerId containerId,
FrostFsObjectId objectId,
FrostFsRange range,
bool raw = false,
CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; } = containerId;
public FrostFsObjectId ObjectId { get; } = objectId;
public FrostFsRange Range { get; } = range;
public bool Raw { get; } = raw;
/// <inheritdoc />
public FrostFsSessionToken? SessionToken { get; set; }
}

View file

@ -0,0 +1,20 @@
namespace FrostFS.SDK.Client;
public sealed class PrmRangeHashGet(
FrostFsContainerId containerId,
FrostFsObjectId objectId,
FrostFsRange[] ranges,
byte[] salt,
CallContext? ctx = null) : PrmBase(ctx), ISessionToken
{
public FrostFsContainerId ContainerId { get; } = containerId;
public FrostFsObjectId ObjectId { get; } = objectId;
public FrostFsRange[] Ranges { get; } = ranges;
public byte[] Salt { get; } = salt;
/// <inheritdoc />
public FrostFsSessionToken? SessionToken { get; set; }
}

View file

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

View file

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

View file

@ -1,6 +1,6 @@
using System;
namespace FrostFS.SDK.ClientV2;
namespace FrostFS.SDK.Client;
public class PrmWait(TimeSpan timeout, TimeSpan pollInterval)
{

View file

@ -0,0 +1,163 @@
using System;
using System.Threading;
using Microsoft.Extensions.Logging;
namespace FrostFS.SDK.Client;
// 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
];
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 ArgumentException("Unknown method", nameof(index)),
};
}
private readonly object _lock = new();
private readonly ILogger? logger;
private int healthy;
public ClientStatusMonitor(ILogger? logger, string address)
{
this.logger = logger;
healthy = (int)HealthyStatus.Healthy;
Address = address;
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; set; }
public uint CurrentErrorCount { get; set; }
public ulong OverallErrorCount { get; set; }
public MethodStatus[] Methods { get; private set; }
public bool IsHealthy()
{
var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
return res;
}
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 != null)
{
FrostFsMessages.ErrorЕhresholdReached(logger, 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;
}
}

View file

@ -0,0 +1,151 @@
using System;
using System.Threading.Tasks;
using Grpc.Core;
namespace FrostFS.SDK.Client;
// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
public class ClientWrapper : ClientStatusMonitor
{
private readonly object _lock = new();
private SessionCache sessionCache;
internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address)
{
WrapperPrm = wrapperPrm;
ErrorThreshold = wrapperPrm.ErrorThreshold;
sessionCache = pool.SessionCache;
Client = new FrostFSClient(WrapperPrm, sessionCache);
}
internal FrostFSClient? Client { get; private set; }
internal WrapperPrm WrapperPrm { get; }
internal FrostFSClient? GetClient()
{
lock (_lock)
{
if (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() ?? throw new FrostFsInvalidObjectException("pool client unhealthy");
await client.Dial(ctx).ConfigureAwait(false);
}
internal void HandleError(Exception ex)
{
if (ex is FrostFsResponseException responseException && responseException.Status != null)
{
switch (responseException.Status.Code)
{
case FrostFsStatusCode.Internal:
case FrostFsStatusCode.WrongMagicNumber:
case FrostFsStatusCode.SignatureVerificationFailure:
case FrostFsStatusCode.NodeUnderMaintenance:
IncErrorRate();
return;
}
}
IncErrorRate();
}
private async Task ScheduleGracefulClose()
{
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<bool> RestartIfUnhealthy(CallContext ctx)
{
bool wasHealthy;
try
{
var prmNodeInfo = new PrmNodeInfo(ctx);
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
return false;
}
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 (IsDialed())
{
await ScheduleGracefulClose().ConfigureAwait(false);
}
FrostFSClient? client = null;
try
{
client = new(WrapperPrm, sessionCache);
//TODO: set additioanl params
var error = await client.Dial(ctx).ConfigureAwait(false);
if (!string.IsNullOrEmpty(error))
{
SetUnhealthyOnDial();
return wasHealthy;
}
lock (_lock)
{
Client = client;
client = null;
}
try
{
var prmNodeInfo = new PrmNodeInfo(ctx);
var res = await Client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
}
catch (FrostFsException)
{
SetUnhealthy();
return wasHealthy;
}
SetHealthy();
return !wasHealthy;
}
finally
{
client?.Dispose();
}
}
internal void IncRequests(ulong elapsed, MethodIndex method)
{
var methodStat = Methods[(int)method];
methodStat.IncRequests(elapsed);
}
}

View file

@ -0,0 +1,18 @@
namespace FrostFS.SDK.Client;
// 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
}

View file

@ -0,0 +1,28 @@
namespace FrostFS.SDK.Client;
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();
}

Some files were not shown because too many files have changed in this diff Show more