Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
cf1fae8d2c | |||
423b5a35b0 | |||
d4c88467d8 | |||
2a71ea3ff9 | |||
c107a219b5 | |||
7125ecae64 | |||
c36a707cb5 |
243 changed files with 4973 additions and 2418 deletions
|
@ -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
|
||||
|
|
22
src/FrostFS.SDK.Client/Caches.cs
Normal file
22
src/FrostFS.SDK.Client/Caches.cs
Normal 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;
|
||||
}
|
14
src/FrostFS.SDK.Client/CllientKey.cs
Normal file
14
src/FrostFS.SDK.Client/CllientKey.cs
Normal 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());
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsException : Exception
|
||||
{
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
18
src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
18
src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
8
src/FrostFS.SDK.Client/GlobalSuppressions.cs
Normal file
8
src/FrostFS.SDK.Client/GlobalSuppressions.cs
Normal 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")]
|
68
src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs
Normal file
68
src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
@ -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();
|
||||
}
|
24
src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs
Normal file
24
src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs
Normal 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);
|
||||
}
|
|
@ -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
|
||||
{
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class NetmapMapper
|
||||
{
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class PlacementPolicyMapper
|
||||
{
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class ReplicaMapper
|
||||
{
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
|
@ -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
|
||||
{
|
|
@ -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,
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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!;
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class SessionMapper
|
||||
{
|
|
@ -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
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class StatusMapper
|
||||
{
|
|
@ -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
|
||||
{
|
62
src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs
Normal file
62
src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs
Normal 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);
|
||||
}
|
||||
}
|
41
src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs
Normal file
41
src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs
Normal 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;
|
||||
}
|
||||
}
|
10
src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public enum FrostFsTargetType
|
||||
{
|
||||
Undefined = 0,
|
||||
Namespace,
|
||||
Container,
|
||||
User,
|
||||
Group
|
||||
}
|
|
@ -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()
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System;
|
|||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.Client;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
|
@ -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)
|
|
@ -1,5 +1,5 @@
|
|||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
48
src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs
Normal file
48
src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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.");
|
||||
|
|
@ -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;
|
29
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal file
29
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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()
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class Credentials(ECDsa key, FrostFsOwner ownerId)
|
||||
{
|
|
@ -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; }
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public interface ISessionToken
|
||||
{
|
6
src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
Normal file
6
src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
5
src/FrostFS.SDK.Client/Parameters/PrmBalance.cs
Normal file
5
src/FrostFS.SDK.Client/Parameters/PrmBalance.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
5
src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs
Normal file
5
src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
}
|
5
src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs
Normal file
5
src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
}
|
5
src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs
Normal file
5
src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
}
|
5
src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs
Normal file
5
src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs
Normal file
|
@ -0,0 +1,5 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
24
src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs
Normal file
24
src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs
Normal 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; }
|
||||
}
|
|
@ -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.
|
|
@ -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
|
20
src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs
Normal file
20
src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs
Normal 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; }
|
||||
}
|
20
src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs
Normal file
20
src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs
Normal 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; }
|
||||
}
|
6
src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs
Normal file
6
src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class PrmWait(TimeSpan timeout, TimeSpan pollInterval)
|
||||
{
|
163
src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs
Normal file
163
src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs
Normal 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;
|
||||
}
|
||||
}
|
151
src/FrostFS.SDK.Client/Pool/ClientWrapper.cs
Normal file
151
src/FrostFS.SDK.Client/Pool/ClientWrapper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
18
src/FrostFS.SDK.Client/Pool/HealthyStatus.cs
Normal file
18
src/FrostFS.SDK.Client/Pool/HealthyStatus.cs
Normal 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
|
||||
}
|
28
src/FrostFS.SDK.Client/Pool/IClientStatus.cs
Normal file
28
src/FrostFS.SDK.Client/Pool/IClientStatus.cs
Normal 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
Loading…
Reference in a new issue