[#26] All: Remove V2 from naming
Rename project, namespaces and class names Signed-off-by: Pavel Gross <p.gross@yadro.com>
This commit is contained in:
parent
c406df1a78
commit
766f61a5f7
219 changed files with 219 additions and 974 deletions
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());
|
||||
}
|
18
src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsException : Exception
|
||||
{
|
||||
public FrostFsException()
|
||||
{
|
||||
}
|
||||
|
||||
public FrostFsException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public FrostFsException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
36
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
36
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
|
@ -0,0 +1,36 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<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.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.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
577
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
577
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
|
@ -0,0 +1,577 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
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.Client.Interfaces;
|
||||
using FrostFS.SDK.Client.Services;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using FrostFS.Session;
|
||||
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Grpc.Net.Client;
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFSClient : IFrostFSClient
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; }
|
||||
|
||||
internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; }
|
||||
|
||||
internal APEManagerService.APEManagerServiceClient? ApeManagerServiceClient { get; set; }
|
||||
|
||||
internal SessionService.SessionServiceClient? SessionServiceClient { get; set; }
|
||||
|
||||
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
|
||||
|
||||
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
|
||||
|
||||
internal ClientContext ClientCtx { get; set; }
|
||||
|
||||
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
||||
{
|
||||
return new FrostFSClient(clientOptions, channelOptions);
|
||||
}
|
||||
|
||||
public static IFrostFSClient GetSingleOwnerInstance(IOptions<SingleOwnerClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
||||
{
|
||||
return new FrostFSClient(clientOptions, channelOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
|
||||
/// </summary>
|
||||
/// <param name="clientOptions">Global setting for client</param>
|
||||
/// <param name="channelOptions">Setting for gRPC channel</param>
|
||||
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
|
||||
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
||||
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
||||
/// <param name="objectService">Object.ObjectService.ObjectServiceClient implementation</param>
|
||||
/// <returns></returns>
|
||||
public static IFrostFSClient GetTestInstance(
|
||||
IOptions<SingleOwnerClientSettings> clientOptions,
|
||||
GrpcChannelOptions? channelOptions,
|
||||
NetmapService.NetmapServiceClient netmapService,
|
||||
SessionService.SessionServiceClient sessionService,
|
||||
ContainerService.ContainerServiceClient containerService,
|
||||
ObjectService.ObjectServiceClient objectService)
|
||||
{
|
||||
if (clientOptions is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientOptions));
|
||||
}
|
||||
|
||||
return new FrostFSClient(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService);
|
||||
}
|
||||
|
||||
private FrostFSClient(
|
||||
IOptions<SingleOwnerClientSettings> settings,
|
||||
GrpcChannelOptions? channelOptions,
|
||||
ContainerService.ContainerServiceClient containerService,
|
||||
NetmapService.NetmapServiceClient netmapService,
|
||||
SessionService.SessionServiceClient sessionService,
|
||||
ObjectService.ObjectServiceClient objectService)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
var ecdsaKey = settings.Value.Key.LoadWif();
|
||||
FrostFsOwner.FromKey(ecdsaKey);
|
||||
|
||||
ClientCtx = new ClientContext(
|
||||
client: this,
|
||||
key: ecdsaKey,
|
||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
|
||||
version: new FrostFsVersion(2, 13));
|
||||
|
||||
ContainerServiceClient = containerService ?? throw new ArgumentNullException(nameof(containerService));
|
||||
NetmapServiceClient = netmapService ?? throw new ArgumentNullException(nameof(netmapService));
|
||||
SessionServiceClient = sessionService ?? throw new ArgumentNullException(nameof(sessionService));
|
||||
ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService));
|
||||
}
|
||||
|
||||
private FrostFSClient(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
|
||||
{
|
||||
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
|
||||
|
||||
clientSettings.Validate();
|
||||
|
||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||
|
||||
ClientCtx = new ClientContext(
|
||||
this,
|
||||
key: null,
|
||||
owner: null,
|
||||
channel: channel,
|
||||
version: new FrostFsVersion(2, 13));
|
||||
|
||||
// TODO: define timeout logic
|
||||
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
||||
}
|
||||
|
||||
private FrostFSClient(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
|
||||
{
|
||||
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
|
||||
|
||||
clientSettings.Validate();
|
||||
|
||||
var ecdsaKey = clientSettings.Key.LoadWif();
|
||||
|
||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||
|
||||
ClientCtx = new ClientContext(
|
||||
this,
|
||||
key: ecdsaKey,
|
||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||
channel: channel,
|
||||
version: new FrostFsVersion(2, 13));
|
||||
|
||||
// TODO: define timeout logic
|
||||
// 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);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !isDisposed)
|
||||
{
|
||||
ClientCtx?.Dispose();
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region ApeManagerImplementation
|
||||
public Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
var service = GetApeManagerService(args);
|
||||
return service.AddChainAsync(args);
|
||||
}
|
||||
|
||||
public Task RemoveChainAsync(PrmApeChainRemove args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
var service = GetApeManagerService(args);
|
||||
return service.RemoveChainAsync(args);
|
||||
}
|
||||
|
||||
public Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetApeManagerService(args);
|
||||
return service.ListChainAsync(args);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContainerImplementation
|
||||
public Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetContainerService(args);
|
||||
return service.GetContainerAsync(args);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
|
||||
{
|
||||
args ??= new PrmContainerGetAll();
|
||||
var service = GetContainerService(args);
|
||||
return service.ListContainersAsync(args);
|
||||
}
|
||||
|
||||
public Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetContainerService(args);
|
||||
return service.CreateContainerAsync(args);
|
||||
}
|
||||
|
||||
public Task DeleteContainerAsync(PrmContainerDelete args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetContainerService(args);
|
||||
return service.DeleteContainerAsync(args);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region NetworkImplementation
|
||||
public Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args)
|
||||
{
|
||||
args ??= new PrmNetmapSnapshot();
|
||||
var service = GetNetmapService(args);
|
||||
return service.GetNetmapSnapshotAsync(args);
|
||||
}
|
||||
|
||||
public Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args)
|
||||
{
|
||||
args ??= new PrmNodeInfo();
|
||||
var service = GetNetmapService(args);
|
||||
return service.GetLocalNodeInfoAsync(args);
|
||||
}
|
||||
|
||||
public Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args)
|
||||
{
|
||||
args ??= new PrmNetworkSettings();
|
||||
var service = GetNetmapService(args);
|
||||
return service.GetNetworkSettingsAsync(args.Context!);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ObjectImplementation
|
||||
public Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
return service.GetObjectHeadAsync(args);
|
||||
}
|
||||
|
||||
public Task<FrostFsObject> GetObjectAsync(PrmObjectGet args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
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<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)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
return service.PutObjectAsync(args);
|
||||
}
|
||||
|
||||
public Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
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)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
return service.DeleteObjectAsync(args);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
return service.SearchObjectsAsync(args);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#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, session.Body.Id.ToUuid());
|
||||
}
|
||||
|
||||
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetSessionService(args);
|
||||
return service.CreateSessionAsync(args);
|
||||
}
|
||||
#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, CallContext ctx)
|
||||
{
|
||||
if (header == null)
|
||||
throw new ArgumentNullException(nameof(header));
|
||||
|
||||
return ObjectTools.CalculateObjectId(header, ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
|
||||
{
|
||||
var args = new PrmNodeInfo(ctx);
|
||||
|
||||
if (ctx?.Version == null)
|
||||
throw new ArgumentNullException(nameof(ctx), "Version must be initialized");
|
||||
|
||||
var service = GetNetmapService(args);
|
||||
var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false);
|
||||
|
||||
if (!localNodeInfo.Version.IsSupported(ctx.Version))
|
||||
{
|
||||
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
|
||||
throw new FrostFsException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private CallInvoker? SetupClientContext(IContext ctx)
|
||||
{
|
||||
if (isDisposed)
|
||||
throw new FrostFsInvalidObjectException("Client is disposed.");
|
||||
|
||||
if (ctx.Context!.Key == null)
|
||||
{
|
||||
if (ClientCtx.Key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ctx), "Key is not initialized.");
|
||||
}
|
||||
|
||||
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
|
||||
}
|
||||
|
||||
if (ctx.Context.OwnerId == null)
|
||||
{
|
||||
ctx.Context.OwnerId = ClientCtx.Owner ?? FrostFsOwner.FromKey(ctx.Context.Key);
|
||||
}
|
||||
|
||||
if (ctx.Context.Version == null)
|
||||
{
|
||||
if (ClientCtx.Version == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ctx), "Version is not initialized.");
|
||||
}
|
||||
|
||||
ctx.Context.Version = ClientCtx.Version;
|
||||
}
|
||||
|
||||
CallInvoker? callInvoker = null;
|
||||
|
||||
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)
|
||||
{
|
||||
if (callInvoker == null)
|
||||
callInvoker = ClientCtx.Channel.Intercept(interceptor);
|
||||
else
|
||||
callInvoker = callInvoker.Intercept(interceptor);
|
||||
|
||||
return callInvoker;
|
||||
}
|
||||
}
|
||||
|
||||
private NetmapServiceProvider GetNetmapService(IContext ctx)
|
||||
{
|
||||
var callInvoker = SetupClientContext(ctx);
|
||||
var client = NetmapServiceClient ?? (callInvoker != null
|
||||
? new NetmapService.NetmapServiceClient(callInvoker)
|
||||
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
||||
|
||||
return new NetmapServiceProvider(client, ClientCtx);
|
||||
}
|
||||
|
||||
private SessionServiceProvider GetSessionService(IContext ctx)
|
||||
{
|
||||
var callInvoker = SetupClientContext(ctx);
|
||||
var client = SessionServiceClient ?? (callInvoker != null
|
||||
? new SessionService.SessionServiceClient(callInvoker)
|
||||
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
||||
|
||||
return new SessionServiceProvider(client, ClientCtx);
|
||||
}
|
||||
|
||||
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
|
||||
{
|
||||
var callInvoker = SetupClientContext(ctx);
|
||||
var client = ApeManagerServiceClient ?? (callInvoker != null
|
||||
? new APEManagerService.APEManagerServiceClient(callInvoker)
|
||||
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
|
||||
|
||||
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 = SetupClientContext(ctx);
|
||||
var client = ContainerServiceClient ?? (callInvoker != null
|
||||
? new ContainerService.ContainerServiceClient(callInvoker)
|
||||
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
||||
|
||||
return new ContainerServiceProvider(client, ClientCtx);
|
||||
}
|
||||
|
||||
private ObjectServiceProvider GetObjectService(IContext ctx)
|
||||
{
|
||||
var callInvoker = SetupClientContext(ctx);
|
||||
var client = ObjectServiceClient ?? (callInvoker != null
|
||||
? new ObjectService.ObjectServiceClient(callInvoker)
|
||||
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
||||
|
||||
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
|
||||
{
|
||||
var uri = new Uri(host);
|
||||
|
||||
if (channelOptions != null)
|
||||
return GrpcChannel.ForAddress(uri, channelOptions);
|
||||
|
||||
return GrpcChannel.ForAddress(uri, new GrpcChannelOptions
|
||||
{
|
||||
HttpHandler = new HttpClientHandler()
|
||||
});
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
75
src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs
Normal file
75
src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
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 MetricsInterceptor(Action<CallStatistics> callback) : 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)
|
||||
{
|
||||
var watch = new Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
var response = await call;
|
||||
|
||||
watch.Stop();
|
||||
|
||||
var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency;
|
||||
|
||||
callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> call)
|
||||
{
|
||||
var watch = new Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
var response = await call;
|
||||
|
||||
watch.Stop();
|
||||
|
||||
var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency;
|
||||
|
||||
callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed });
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
72
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
72
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Frostfs.V2.Ape;
|
||||
|
||||
namespace FrostFS.SDK.Client.Interfaces;
|
||||
|
||||
public interface IFrostFSClient : IDisposable
|
||||
{
|
||||
#region Network
|
||||
Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null);
|
||||
|
||||
Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args = null);
|
||||
|
||||
Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args = null);
|
||||
#endregion
|
||||
|
||||
#region Session
|
||||
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args);
|
||||
#endregion
|
||||
|
||||
#region ApeManager
|
||||
Task<byte[]> AddChainAsync(PrmApeChainAdd args);
|
||||
|
||||
Task RemoveChainAsync(PrmApeChainRemove args);
|
||||
|
||||
Task<Chain[]> ListChainAsync(PrmApeChainList args);
|
||||
#endregion
|
||||
|
||||
#region Container
|
||||
Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args);
|
||||
|
||||
IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null);
|
||||
|
||||
Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args);
|
||||
|
||||
Task DeleteContainerAsync(PrmContainerDelete args);
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args);
|
||||
|
||||
Task<FrostFsObject> GetObjectAsync(PrmObjectGet args);
|
||||
|
||||
Task<RangeReader> GetRangeAsync(PrmRangeGet args);
|
||||
|
||||
Task<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 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);
|
||||
}
|
22
src/FrostFS.SDK.Client/Mappers/Container.cs
Normal file
22
src/FrostFS.SDK.Client/Mappers/Container.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ContainerMapper
|
||||
{
|
||||
public static FrostFsContainerInfo ToModel(this Container.Container container)
|
||||
{
|
||||
if (container == null)
|
||||
throw new ArgumentNullException(nameof(container));
|
||||
|
||||
return new FrostFsContainerInfo(
|
||||
container.PlacementPolicy.ToModel(),
|
||||
container.Attributes?.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToArray(),
|
||||
container.Version?.ToModel(),
|
||||
container.OwnerId?.ToModel(),
|
||||
container.Nonce?.ToUuid());
|
||||
}
|
||||
}
|
48
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
48
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ContainerIdMapper
|
||||
{
|
||||
private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
|
||||
.SetSlidingExpiration(TimeSpan.FromHours(1))
|
||||
.SetSize(1);
|
||||
|
||||
public static ContainerID ToMessage(this FrostFsContainerId model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
}
|
||||
|
||||
var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model));
|
||||
|
||||
if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message))
|
||||
{
|
||||
message = new ContainerID
|
||||
{
|
||||
Value = ByteString.CopyFrom(Base58.Decode(containerId))
|
||||
};
|
||||
|
||||
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.ToByteArray()));
|
||||
}
|
||||
}
|
23
src/FrostFS.SDK.Client/Mappers/MetaHeader.cs
Normal file
23
src/FrostFS.SDK.Client/Mappers/MetaHeader.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Session;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class MetaHeaderMapper
|
||||
{
|
||||
public static RequestMetaHeader ToMessage(this MetaHeader metaHeader)
|
||||
{
|
||||
if (metaHeader is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metaHeader));
|
||||
}
|
||||
|
||||
return new RequestMetaHeader
|
||||
{
|
||||
Version = metaHeader.Version.ToMessage(),
|
||||
Epoch = (uint)metaHeader.Epoch,
|
||||
Ttl = (uint)metaHeader.Ttl
|
||||
};
|
||||
}
|
||||
}
|
23
src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs
Normal file
23
src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class NetmapMapper
|
||||
{
|
||||
public static FrostFsNetmapSnapshot ToModel(this NetmapSnapshotResponse netmap)
|
||||
{
|
||||
if (netmap is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(netmap));
|
||||
}
|
||||
|
||||
return new FrostFsNetmapSnapshot(
|
||||
netmap.Body.Netmap.Epoch,
|
||||
netmap.Body.Netmap.Nodes
|
||||
.Select(n => n.ToModel(netmap.MetaHeader.Version))
|
||||
.ToArray());
|
||||
}
|
||||
}
|
45
src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs
Normal file
45
src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class NodeInfoMapper
|
||||
{
|
||||
public static FrostFsNodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node)
|
||||
{
|
||||
if (node is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(node));
|
||||
}
|
||||
|
||||
return node.NodeInfo.ToModel(node.Version);
|
||||
}
|
||||
|
||||
public static FrostFsNodeInfo ToModel(this NodeInfo nodeInfo, Refs.Version version)
|
||||
{
|
||||
if (nodeInfo is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(nodeInfo));
|
||||
}
|
||||
|
||||
NodeState state = nodeInfo.State switch
|
||||
{
|
||||
NodeInfo.Types.State.Unspecified => NodeState.Unspecified,
|
||||
NodeInfo.Types.State.Online => NodeState.Online,
|
||||
NodeInfo.Types.State.Offline => NodeState.Offline,
|
||||
NodeInfo.Types.State.Maintenance => NodeState.Maintenance,
|
||||
_ => throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.State}'.")
|
||||
};
|
||||
|
||||
return new FrostFsNodeInfo(
|
||||
version: version.ToModel(),
|
||||
state: state,
|
||||
addresses: [.. nodeInfo.Addresses],
|
||||
attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value),
|
||||
publicKey: nodeInfo.PublicKey.ToByteArray()
|
||||
);
|
||||
}
|
||||
}
|
22
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
22
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class PlacementPolicyMapper
|
||||
{
|
||||
public static FrostFsPlacementPolicy ToModel(this PlacementPolicy placementPolicy)
|
||||
{
|
||||
if (placementPolicy is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(placementPolicy));
|
||||
}
|
||||
|
||||
return new FrostFsPlacementPolicy(
|
||||
placementPolicy.Unique,
|
||||
placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||
);
|
||||
}
|
||||
}
|
27
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
27
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class ReplicaMapper
|
||||
{
|
||||
public static Replica ToMessage(this FrostFsReplica replica)
|
||||
{
|
||||
return new Replica
|
||||
{
|
||||
Count = (uint)replica.Count,
|
||||
Selector = replica.Selector
|
||||
};
|
||||
}
|
||||
|
||||
public static FrostFsReplica ToModel(this Replica replica)
|
||||
{
|
||||
if (replica is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(replica));
|
||||
}
|
||||
|
||||
return new FrostFsReplica((int)replica.Count, replica.Selector);
|
||||
}
|
||||
}
|
12
src/FrostFS.SDK.Client/Mappers/Object/Object.cs
Normal file
12
src/FrostFS.SDK.Client/Mappers/Object/Object.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
internal static class ObjectMapper
|
||||
{
|
||||
internal static FrostFsObject ToModel(this Object.Object obj)
|
||||
{
|
||||
return new FrostFsObject(obj.Header.ToModel())
|
||||
{
|
||||
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray())
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Object;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectAttributeMapper
|
||||
{
|
||||
public static Header.Types.Attribute ToMessage(this FrostFsAttributePair attribute)
|
||||
{
|
||||
if (attribute is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return new Header.Types.Attribute
|
||||
{
|
||||
Key = attribute.Key,
|
||||
Value = attribute.Value
|
||||
};
|
||||
}
|
||||
|
||||
public static FrostFsAttributePair ToModel(this Header.Types.Attribute attribute)
|
||||
{
|
||||
if (attribute is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return new FrostFsAttributePair(attribute.Key, attribute.Value);
|
||||
}
|
||||
}
|
34
src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs
Normal file
34
src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Object;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectFilterMapper
|
||||
{
|
||||
public static SearchRequest.Types.Body.Types.Filter ToMessage(this IObjectFilter filter)
|
||||
{
|
||||
if (filter is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
|
||||
var objMatchTypeName = filter.MatchType switch
|
||||
{
|
||||
FrostFsMatchType.Unspecified => MatchType.Unspecified,
|
||||
FrostFsMatchType.Equals => MatchType.StringEqual,
|
||||
FrostFsMatchType.NotEquals => MatchType.StringNotEqual,
|
||||
FrostFsMatchType.KeyAbsent => MatchType.NotPresent,
|
||||
FrostFsMatchType.StartsWith => MatchType.CommonPrefix,
|
||||
|
||||
_ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.")
|
||||
};
|
||||
|
||||
return new SearchRequest.Types.Body.Types.Filter
|
||||
{
|
||||
MatchType = objMatchTypeName,
|
||||
Key = filter.Key,
|
||||
Value = filter.GetSerializedValue()
|
||||
};
|
||||
}
|
||||
}
|
55
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
55
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Object;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectHeaderMapper
|
||||
{
|
||||
public static FrostFsObjectHeader ToModel(this Header header)
|
||||
{
|
||||
if (header is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(header));
|
||||
}
|
||||
|
||||
var objTypeName = header.ObjectType switch
|
||||
{
|
||||
ObjectType.Regular => FrostFsObjectType.Regular,
|
||||
ObjectType.Lock => FrostFsObjectType.Lock,
|
||||
ObjectType.Tombstone => FrostFsObjectType.Tombstone,
|
||||
_ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.")
|
||||
};
|
||||
|
||||
FrostFsSplit? split = null;
|
||||
|
||||
if (header.Split != null)
|
||||
{
|
||||
var children = header.Split.Children.Count != 0 ? new ReadOnlyCollection<FrostFsObjectId>(
|
||||
header.Split.Children.Select(x => x.ToModel()).ToList()) : null;
|
||||
|
||||
split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid()),
|
||||
header.Split.Previous?.ToModel(),
|
||||
header.Split.Parent?.ToModel(),
|
||||
header.Split.ParentHeader?.ToModel(),
|
||||
null,
|
||||
children);
|
||||
}
|
||||
|
||||
var model = new FrostFsObjectHeader(
|
||||
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())),
|
||||
objTypeName,
|
||||
header.Attributes.Select(attribute => attribute.ToModel()).ToArray(),
|
||||
split,
|
||||
header.OwnerId.ToModel(),
|
||||
header.Version.ToModel())
|
||||
{
|
||||
PayloadLength = header.PayloadLength,
|
||||
};
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
33
src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs
Normal file
33
src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Refs;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectIdMapper
|
||||
{
|
||||
public static ObjectID ToMessage(this FrostFsObjectId objectId)
|
||||
{
|
||||
if (objectId is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectId));
|
||||
}
|
||||
|
||||
return new ObjectID
|
||||
{
|
||||
Value = ByteString.CopyFrom(objectId.ToHash())
|
||||
};
|
||||
}
|
||||
|
||||
public static FrostFsObjectId ToModel(this ObjectID objectId)
|
||||
{
|
||||
if (objectId is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectId));
|
||||
}
|
||||
|
||||
return FrostFsObjectId.FromHash(objectId.Value.ToByteArray());
|
||||
}
|
||||
}
|
54
src/FrostFS.SDK.Client/Mappers/OwnerId.cs
Normal file
54
src/FrostFS.SDK.Client/Mappers/OwnerId.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class OwnerIdMapper
|
||||
{
|
||||
private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
|
||||
.SetSlidingExpiration(TimeSpan.FromHours(1))
|
||||
.SetSize(1);
|
||||
|
||||
public static OwnerID ToMessage(this FrostFsOwner model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
}
|
||||
|
||||
if (!Caches.Owners.TryGetValue(model, out OwnerID? message))
|
||||
{
|
||||
message = new OwnerID
|
||||
{
|
||||
Value = ByteString.CopyFrom(model.ToHash())
|
||||
};
|
||||
|
||||
Caches.Owners.Set(model, message, _oneHourExpiration);
|
||||
}
|
||||
|
||||
return message!;
|
||||
}
|
||||
|
||||
public static FrostFsOwner ToModel(this OwnerID message)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model))
|
||||
{
|
||||
model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray()));
|
||||
|
||||
Caches.Owners.Set(message, model, _oneHourExpiration);
|
||||
}
|
||||
|
||||
return model!;
|
||||
}
|
||||
}
|
28
src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs
Normal file
28
src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class SessionMapper
|
||||
{
|
||||
public static byte[] Serialize(this Session.SessionToken token)
|
||||
{
|
||||
if (token is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[token.CalculateSize()];
|
||||
using CodedOutputStream stream = new(bytes);
|
||||
token.WriteTo(stream);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static Session.SessionToken Deserialize(this Session.SessionToken token, byte[] bytes)
|
||||
{
|
||||
token.MergeFrom(bytes);
|
||||
return token;
|
||||
}
|
||||
}
|
31
src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs
Normal file
31
src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class SignatureMapper
|
||||
{
|
||||
public static Refs.Signature ToMessage(this FrostFsSignature signature)
|
||||
{
|
||||
if (signature is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(signature));
|
||||
}
|
||||
|
||||
var scheme = signature.Scheme switch
|
||||
{
|
||||
SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256,
|
||||
SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect,
|
||||
SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512,
|
||||
_ => throw new ArgumentException(nameof(signature.Scheme), $"Unexpected enum value: {signature.Scheme}")
|
||||
};
|
||||
|
||||
return new Refs.Signature
|
||||
{
|
||||
Key = ByteString.CopyFrom(signature.Key),
|
||||
Scheme = scheme,
|
||||
Sign = ByteString.CopyFrom(signature.Sign)
|
||||
};
|
||||
}
|
||||
}
|
18
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
18
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class StatusMapper
|
||||
{
|
||||
public static FrostFsResponseStatus ToModel(this Status.Status status)
|
||||
{
|
||||
if (status is null)
|
||||
return new FrostFsResponseStatus(FrostFsStatusCode.Success);
|
||||
|
||||
var codeName = Enum.GetName(typeof(FrostFsStatusCode), status.Code);
|
||||
|
||||
return codeName is null
|
||||
? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.")
|
||||
: new FrostFsResponseStatus((FrostFsStatusCode)status.Code, status.Message);
|
||||
}
|
||||
}
|
85
src/FrostFS.SDK.Client/Mappers/Version.cs
Normal file
85
src/FrostFS.SDK.Client/Mappers/Version.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using System.Collections;
|
||||
using System.Threading;
|
||||
|
||||
using FrostFS.Refs;
|
||||
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class VersionMapper
|
||||
{
|
||||
private static readonly Hashtable _cacheMessages = [];
|
||||
private static readonly Hashtable _cacheModels = [];
|
||||
private static SpinLock _spinlock;
|
||||
|
||||
public static Version ToMessage(this FrostFsVersion model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(model));
|
||||
}
|
||||
|
||||
var key = model.Major << 16 + model.Minor;
|
||||
|
||||
if (!_cacheMessages.ContainsKey(key))
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
_spinlock.Enter(ref lockTaken);
|
||||
var message = new Version
|
||||
{
|
||||
Major = (uint)model.Major,
|
||||
Minor = (uint)model.Minor
|
||||
};
|
||||
|
||||
_cacheMessages.Add(key, message);
|
||||
return message;
|
||||
}
|
||||
catch (System.ArgumentException)
|
||||
{
|
||||
// ignore attempt to add duplicate
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
_spinlock.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (Version)_cacheMessages[key];
|
||||
}
|
||||
|
||||
public static FrostFsVersion ToModel(this Version message)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
var key = (int)message.Major << 16 + (int)message.Minor;
|
||||
|
||||
if (!_cacheModels.ContainsKey(key))
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
_spinlock.Enter(ref lockTaken);
|
||||
var model = new FrostFsVersion((int)message.Major, (int)message.Minor);
|
||||
|
||||
_cacheModels.Add(key, model);
|
||||
return model;
|
||||
}
|
||||
catch (System.ArgumentException)
|
||||
{
|
||||
// ignore attempt to add duplicate
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
_spinlock.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (FrostFsVersion)_cacheModels[key];
|
||||
}
|
||||
}
|
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();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
70
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
70
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class ClientSettings
|
||||
{
|
||||
protected static readonly string errorTemplate = "{0} is required parameter";
|
||||
|
||||
public string Host { get; set; } = string.Empty;
|
||||
|
||||
public virtual void Validate()
|
||||
{
|
||||
var errors = CheckFields();
|
||||
if (errors != null)
|
||||
ThrowSettingsException(errors);
|
||||
}
|
||||
|
||||
protected Collection<string>? CheckFields()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Host))
|
||||
{
|
||||
var error = string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host));
|
||||
return new Collection<string>([error]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static void ThrowSettingsException(Collection<string> errors)
|
||||
{
|
||||
if (errors is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errors));
|
||||
}
|
||||
|
||||
StringBuilder messages = new();
|
||||
|
||||
foreach (var error in errors)
|
||||
{
|
||||
messages.AppendLine(error);
|
||||
}
|
||||
|
||||
throw new ArgumentException(messages.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public class SingleOwnerClientSettings : ClientSettings
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
public override void Validate()
|
||||
{
|
||||
var errors = CheckFields();
|
||||
if (errors != null)
|
||||
ThrowSettingsException(errors);
|
||||
}
|
||||
|
||||
protected new Collection<string>? CheckFields()
|
||||
{
|
||||
Collection<string>? errors = base.CheckFields();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Key))
|
||||
(errors ??= []).Add(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key)));
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsContainerId
|
||||
{
|
||||
private string? modelId;
|
||||
private ContainerID? containerID;
|
||||
|
||||
public FrostFsContainerId(string id)
|
||||
{
|
||||
this.modelId = id;
|
||||
}
|
||||
|
||||
internal FrostFsContainerId(ContainerID id)
|
||||
{
|
||||
this.containerID = id;
|
||||
}
|
||||
|
||||
public string GetValue()
|
||||
{
|
||||
if (this.modelId != null)
|
||||
return this.modelId;
|
||||
|
||||
if (containerID != null)
|
||||
{
|
||||
this.modelId = Base58.Encode(containerID.Value.ToByteArray());
|
||||
return this.modelId;
|
||||
}
|
||||
|
||||
throw new FrostFsInvalidObjectException();
|
||||
}
|
||||
|
||||
internal ContainerID ContainerID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.containerID != null)
|
||||
return this.containerID;
|
||||
|
||||
if (modelId != null)
|
||||
{
|
||||
this.containerID = this.ToMessage();
|
||||
return this.containerID;
|
||||
}
|
||||
|
||||
throw new FrostFsInvalidObjectException();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetValue();
|
||||
}
|
||||
}
|
109
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
109
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsContainerInfo
|
||||
{
|
||||
private Container.Container.Types.Attribute[]? grpsAttributes;
|
||||
private ReadOnlyCollection<FrostFsAttributePair>? attributes;
|
||||
private FrostFsPlacementPolicy? placementPolicy;
|
||||
private Guid? nonce;
|
||||
|
||||
private Container.Container? container;
|
||||
|
||||
public FrostFsContainerInfo(
|
||||
FrostFsPlacementPolicy placementPolicy,
|
||||
FrostFsAttributePair[]? attributes = null,
|
||||
FrostFsVersion? version = null,
|
||||
FrostFsOwner? owner = null,
|
||||
Guid? nonce = null)
|
||||
{
|
||||
this.placementPolicy = placementPolicy;
|
||||
Version = version;
|
||||
Owner = owner;
|
||||
this.nonce = nonce;
|
||||
|
||||
if (attributes != null)
|
||||
this.attributes = new ReadOnlyCollection<FrostFsAttributePair>(attributes);
|
||||
}
|
||||
|
||||
internal FrostFsContainerInfo(Container.Container container)
|
||||
{
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public Guid Nonce
|
||||
{
|
||||
get
|
||||
{
|
||||
nonce ??= container?.Nonce != null ? container.Nonce.ToUuid() : Guid.NewGuid();
|
||||
return nonce.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public FrostFsPlacementPolicy? PlacementPolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
placementPolicy ??= container?.PlacementPolicy?.ToModel();
|
||||
return placementPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<FrostFsAttributePair>? Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (attributes == null && grpsAttributes != null)
|
||||
attributes = new ReadOnlyCollection<FrostFsAttributePair>(grpsAttributes.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToList());
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public FrostFsVersion? Version { get; private set; }
|
||||
|
||||
public FrostFsOwner? Owner { get; private set; }
|
||||
|
||||
internal Container.Container.Types.Attribute[]? GetGrpsAttributes()
|
||||
{
|
||||
grpsAttributes ??= Attributes?
|
||||
.Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value })
|
||||
.ToArray();
|
||||
|
||||
return grpsAttributes;
|
||||
}
|
||||
|
||||
internal Container.Container GetContainer()
|
||||
{
|
||||
if (this.container == null)
|
||||
{
|
||||
if (PlacementPolicy == null)
|
||||
{
|
||||
throw new ArgumentNullException("PlacementPolicy is null");
|
||||
}
|
||||
|
||||
this.container = new Container.Container()
|
||||
{
|
||||
PlacementPolicy = PlacementPolicy.Value.GetPolicy(),
|
||||
Nonce = ByteString.CopyFrom(Nonce.ToBytes()),
|
||||
OwnerId = Owner?.OwnerID,
|
||||
Version = Version?.Version
|
||||
};
|
||||
|
||||
var attribs = GetGrpsAttributes();
|
||||
if (attribs != null)
|
||||
this.container.Attributes.AddRange(attribs);
|
||||
}
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
10
src/FrostFS.SDK.Client/Models/Enums/FrostFsMatchType.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Enums/FrostFsMatchType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public enum FrostFsMatchType
|
||||
{
|
||||
Unspecified = 0,
|
||||
Equals = 1,
|
||||
NotEquals = 2,
|
||||
KeyAbsent = 3,
|
||||
StartsWith = 4
|
||||
}
|
8
src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public enum FrostFsObjectType
|
||||
{
|
||||
Regular = 0,
|
||||
Tombstone = 1,
|
||||
Lock = 3
|
||||
}
|
22
src/FrostFS.SDK.Client/Models/Enums/FrostFsStatusCode.cs
Normal file
22
src/FrostFS.SDK.Client/Models/Enums/FrostFsStatusCode.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public enum FrostFsStatusCode
|
||||
{
|
||||
Success = 0,
|
||||
Internal = 1024,
|
||||
WrongMagicNumber = 1025,
|
||||
SignatureVerificationFailure = 1026,
|
||||
NodeUnderMaintenance = 1027,
|
||||
ObjectAccessDenied = 2048,
|
||||
ObjectNotFound = 2049,
|
||||
ObjectLocked = 2050,
|
||||
LockNotRegularObject = 2051,
|
||||
ObjectAlreadyRemoved = 2052,
|
||||
OutOfRange = 2053,
|
||||
ContainerNotFound = 3072,
|
||||
EAclNotFound = 3073,
|
||||
ContainerAccessDenied = 3074,
|
||||
TokenNotFound = 4096,
|
||||
TokenExpired = 4097,
|
||||
ApeManagerAccessDenied = 5120
|
||||
}
|
9
src/FrostFS.SDK.Client/Models/Enums/NodeState.cs
Normal file
9
src/FrostFS.SDK.Client/Models/Enums/NodeState.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public enum NodeState
|
||||
{
|
||||
Unspecified = 0,
|
||||
Online = 1,
|
||||
Offline = 2,
|
||||
Maintenance = 3
|
||||
}
|
8
src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public enum SignatureScheme
|
||||
{
|
||||
EcdsaSha512,
|
||||
EcdsaRfc6979Sha256,
|
||||
EcdsaRfc6979Sha256WalletConnect
|
||||
}
|
7
src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs
Normal file
7
src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class CallStatistics
|
||||
{
|
||||
public string? MethodName { get; set; }
|
||||
public long ElapsedMicroSeconds { get; set; }
|
||||
}
|
21
src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs
Normal file
21
src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class CheckSum
|
||||
{
|
||||
private byte[]? hash;
|
||||
private string? text;
|
||||
|
||||
public static CheckSum CreateCheckSum(byte[] content)
|
||||
{
|
||||
return new CheckSum { hash = content.Sha256() };
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return text ??= BitConverter.ToString(hash).Replace("-", "");
|
||||
}
|
||||
}
|
52
src/FrostFS.SDK.Client/Models/Misc/Constants.cs
Normal file
52
src/FrostFS.SDK.Client/Models/Misc/Constants.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const int ObjectChunkSize = 3 * (1 << 20);
|
||||
public const int Sha256HashLength = 32;
|
||||
|
||||
// HeaderPrefix is a prefix of key to object header value or property.
|
||||
public const string HeaderPrefix = "$Object:";
|
||||
|
||||
// FilterHeaderVersion is a filter key to "version" field of the object header.
|
||||
public const string FilterHeaderVersion = HeaderPrefix + "version";
|
||||
|
||||
// FilterHeaderObjectID is a filter key to "object_id" field of the object.
|
||||
public const string FilterHeaderObjectID = HeaderPrefix + "objectID";
|
||||
|
||||
// FilterHeaderContainerID is a filter key to "container_id" field of the object header.
|
||||
public const string FilterHeaderContainerID = HeaderPrefix + "containerID";
|
||||
|
||||
// FilterHeaderOwnerID is a filter key to "owner_id" field of the object header.
|
||||
public const string FilterHeaderOwnerID = HeaderPrefix + "ownerID";
|
||||
|
||||
// FilterHeaderCreationEpoch is a filter key to "creation_epoch" field of the object header.
|
||||
public const string FilterHeaderCreationEpoch = HeaderPrefix + "creationEpoch";
|
||||
|
||||
// FilterHeaderPayloadLength is a filter key to "payload_length" field of the object header.
|
||||
public const string FilterHeaderPayloadLength = HeaderPrefix + "payloadLength";
|
||||
|
||||
// FilterHeaderPayloadHash is a filter key to "payload_hash" field of the object header.
|
||||
public const string FilterHeaderPayloadHash = HeaderPrefix + "payloadHash";
|
||||
|
||||
// FilterHeaderObjectType is a filter key to "object_type" field of the object header.
|
||||
public const string FilterHeaderObjectType = HeaderPrefix + "objectType";
|
||||
|
||||
// FilterHeaderHomomorphicHash is a filter key to "homomorphic_hash" field of the object header.
|
||||
public const string FilterHeaderHomomorphicHash = HeaderPrefix + "homomorphicHash";
|
||||
|
||||
// FilterHeaderParent is a filter key to "split.parent" field of the object header.
|
||||
public const string FilterHeaderParent = HeaderPrefix + "split.parent";
|
||||
|
||||
// FilterHeaderSplitID is a filter key to "split.splitID" field of the object header.
|
||||
public const string FilterHeaderSplitID = HeaderPrefix + "split.splitID";
|
||||
|
||||
// FilterHeaderECParent is a filter key to "ec.parent" field of the object header.
|
||||
public const string FilterHeaderECParent = HeaderPrefix + "ec.parent";
|
||||
|
||||
// FilterPropertyRoot is a filter key to check if regular object is on top of split hierarchy.
|
||||
public const string FilterHeaderRoot = HeaderPrefix + "ROOT";
|
||||
|
||||
// FilterPropertyPhy is a filter key to check if an object physically stored on a node.
|
||||
public const string FilterHeaderPhy = HeaderPrefix + "PHY";
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList<FrostFsNodeInfo> nodeInfoCollection)
|
||||
{
|
||||
public ulong Epoch { get; private set; } = epoch;
|
||||
|
||||
public IReadOnlyList<FrostFsNodeInfo> NodeInfoCollection { get; private set; } = nodeInfoCollection;
|
||||
}
|
18
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
18
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsNodeInfo(
|
||||
FrostFsVersion version,
|
||||
NodeState state,
|
||||
IReadOnlyCollection<string> addresses,
|
||||
IReadOnlyDictionary<string, string> attributes,
|
||||
ReadOnlyMemory<byte> publicKey)
|
||||
{
|
||||
public NodeState State { get; private set; } = state;
|
||||
public FrostFsVersion Version { get; private set; } = version;
|
||||
public IReadOnlyCollection<string> Addresses { get; private set; } = addresses;
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; private set; } = attributes;
|
||||
public ReadOnlyMemory<byte> PublicKey { get; private set; } = publicKey;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.SDK.Client;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas)
|
||||
: IEquatable<FrostFsPlacementPolicy>
|
||||
{
|
||||
private PlacementPolicy policy;
|
||||
|
||||
public FrostFsReplica[] Replicas { get; private set; } = replicas;
|
||||
public bool Unique { get; private set; } = unique;
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
return false;
|
||||
|
||||
var other = (FrostFsPlacementPolicy)obj;
|
||||
|
||||
return Equals(other);
|
||||
}
|
||||
|
||||
public PlacementPolicy GetPolicy()
|
||||
{
|
||||
if (policy == null)
|
||||
{
|
||||
policy = new PlacementPolicy
|
||||
{
|
||||
Filters = { },
|
||||
Selectors = { },
|
||||
Replicas = { },
|
||||
Unique = Unique
|
||||
};
|
||||
|
||||
foreach (var replica in Replicas)
|
||||
{
|
||||
policy.Replicas.Add(replica.ToMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
//public static FrostFsPlacementPolicy ToModel(placementPolicy)
|
||||
//{
|
||||
// return new FrostFsPlacementPolicy(
|
||||
// placementPolicy.Unique,
|
||||
// placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Unique ? 17 : 0 + Replicas.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public readonly bool Equals(FrostFsPlacementPolicy other)
|
||||
{
|
||||
var notEqual = Unique != other.Unique
|
||||
|| Replicas.Length != other.Replicas.Length;
|
||||
|
||||
if (notEqual)
|
||||
return false;
|
||||
|
||||
foreach (var replica in Replicas)
|
||||
{
|
||||
if (!other.Replicas.Any(r => r.Equals(replica)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
47
src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs
Normal file
47
src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public struct FrostFsReplica : IEquatable<FrostFsReplica>
|
||||
{
|
||||
public int Count { get; set; }
|
||||
public string Selector { get; set; }
|
||||
|
||||
public FrostFsReplica(int count, string? selector = null)
|
||||
{
|
||||
selector ??= string.Empty;
|
||||
|
||||
Count = count;
|
||||
Selector = selector;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
return false;
|
||||
|
||||
var other = (FrostFsReplica)obj;
|
||||
|
||||
return Count == other.Count && Selector == other.Selector;
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Count + Selector.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(FrostFsReplica left, FrostFsReplica right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(FrostFsReplica left, FrostFsReplica right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public readonly bool Equals(FrostFsReplica other)
|
||||
{
|
||||
return Count == other.Count && Selector == other.Selector;
|
||||
}
|
||||
}
|
36
src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs
Normal file
36
src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsVersion(int major, int minor)
|
||||
{
|
||||
private Version? version;
|
||||
|
||||
public int Major { get; set; } = major;
|
||||
public int Minor { get; set; } = minor;
|
||||
|
||||
internal Version Version
|
||||
{
|
||||
get
|
||||
{
|
||||
this.version ??= this.ToMessage();
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupported(FrostFsVersion version)
|
||||
{
|
||||
if (version is null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(version));
|
||||
}
|
||||
|
||||
return Major == version.Major;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"v{Major}.{Minor}";
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsAttributePair(string key, string value)
|
||||
{
|
||||
public string Key { get; set; } = key;
|
||||
|
||||
public string Value { get; set; } = value;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsLargeObject(FrostFsContainerId container) : FrostFsObject(container)
|
||||
{
|
||||
public ulong PayloadLength
|
||||
{
|
||||
get { return Header!.PayloadLength; }
|
||||
}
|
||||
}
|
21
src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs
Normal file
21
src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsLinkObject : FrostFsObject
|
||||
{
|
||||
public FrostFsLinkObject(FrostFsContainerId containerId,
|
||||
SplitId splitId,
|
||||
FrostFsObjectHeader largeObjectHeader,
|
||||
IList<FrostFsObjectId> children)
|
||||
: base(containerId)
|
||||
{
|
||||
Header!.Split = new FrostFsSplit(splitId,
|
||||
null,
|
||||
null,
|
||||
largeObjectHeader,
|
||||
null,
|
||||
new ReadOnlyCollection<FrostFsObjectId>(children));
|
||||
}
|
||||
}
|
74
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
74
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsObject
|
||||
{
|
||||
private byte[]? bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates new instance from <c>ObjectHeader</c>
|
||||
/// </summary>
|
||||
/// <param name="header"></param> <summary>
|
||||
public FrostFsObject(FrostFsObjectHeader header)
|
||||
{
|
||||
Header = header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new instance with specified parameters
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
/// <param name="objectType"></param>
|
||||
public FrostFsObject(FrostFsContainerId container, FrostFsObjectType objectType = FrostFsObjectType.Regular)
|
||||
{
|
||||
Header = new FrostFsObjectHeader(containerId: container, type: objectType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Header contains metadata for the object
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public FrostFsObjectHeader Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed.
|
||||
/// </summary>
|
||||
public FrostFsObjectId? ObjectId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A payload is obtained via stream reader
|
||||
/// </summary>
|
||||
/// <value>Reader for received data</value>
|
||||
public IObjectReader? ObjectReader { get; set; }
|
||||
|
||||
internal byte[] SingleObjectPayload
|
||||
{
|
||||
get { return bytes ?? []; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size of payload cannot exceed <c>MaxObjectSize</c> value from <c>NetworkSettings</c>
|
||||
/// Used only for PutSingleObject method
|
||||
/// </summary>
|
||||
/// <value>Buffer for output data</value>
|
||||
public void SetSingleObjectPayload(byte[] bytes)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applied only for the last Object in chain in case of manual multipart uploading
|
||||
/// </summary>
|
||||
/// <param name="largeObject">Parent for multipart object</param>
|
||||
public void SetParent(FrostFsObjectHeader largeObjectHeader)
|
||||
{
|
||||
if (Header?.Split == null)
|
||||
throw new ArgumentNullException(nameof(largeObjectHeader), "Split value must not be null");
|
||||
|
||||
Header.Split.ParentHeader = largeObjectHeader;
|
||||
}
|
||||
}
|
111
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs
Normal file
111
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public interface IObjectFilter
|
||||
{
|
||||
public FrostFsMatchType MatchType { get; set; }
|
||||
public string Key { get; set; }
|
||||
|
||||
string? GetSerializedValue();
|
||||
}
|
||||
|
||||
public abstract class FrostFsObjectFilter<T>(FrostFsMatchType matchType, string key, T value) : IObjectFilter
|
||||
{
|
||||
public FrostFsMatchType MatchType { get; set; } = matchType;
|
||||
public string Key { get; set; } = key;
|
||||
|
||||
public T Value { get; set; } = value;
|
||||
|
||||
public string? GetSerializedValue()
|
||||
{
|
||||
return Value?.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Attribute
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="key">Attribute key</param>
|
||||
/// <param name="value">Attribute value</param>
|
||||
public class FilterByAttributePair(FrostFsMatchType matchType, string key, string value) : FrostFsObjectFilter<string>(matchType, key, value) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by ObjectId
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="objectId">ObjectId</param>
|
||||
public class FilterByObjectId(FrostFsMatchType matchType, FrostFsObjectId objectId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderObjectID, objectId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by OwnerId
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="ownerId">ObjectId</param>
|
||||
public class FilterByOwnerId(FrostFsMatchType matchType, FrostFsOwner ownerId) : FrostFsObjectFilter<FrostFsOwner>(matchType, Constants.FilterHeaderOwnerID, ownerId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Version
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="version">Version</param>
|
||||
public class FilterByVersion(FrostFsMatchType matchType, FrostFsVersion version) : FrostFsObjectFilter<FrostFsVersion>(matchType, Constants.FilterHeaderVersion, version) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by ContainerId
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="containerId">ContainerId</param>
|
||||
public class FilterByContainerId(FrostFsMatchType matchType, FrostFsContainerId containerId) : FrostFsObjectFilter<FrostFsContainerId>(matchType, Constants.FilterHeaderContainerID, containerId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by creation Epoch
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="epoch">Creation Epoch</param>
|
||||
public class FilterByEpoch(FrostFsMatchType matchType, ulong epoch) : FrostFsObjectFilter<ulong>(matchType, Constants.FilterHeaderCreationEpoch, epoch) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Payload Length
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="payloadLength">Payload Length</param>
|
||||
public class FilterByPayloadLength(FrostFsMatchType matchType, ulong payloadLength) : FrostFsObjectFilter<ulong>(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Payload Hash
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="payloadHash">Payload Hash</param>
|
||||
public class FilterByPayloadHash(FrostFsMatchType matchType, CheckSum payloadHash) : FrostFsObjectFilter<CheckSum>(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Parent
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="parentId">Parent</param>
|
||||
public class FilterByParent(FrostFsMatchType matchType, FrostFsObjectId parentId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderParent, parentId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by SplitId
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="splitId">SplitId</param>
|
||||
public class FilterBySplitId(FrostFsMatchType matchType, SplitId splitId) : FrostFsObjectFilter<SplitId>(matchType, Constants.FilterHeaderSplitID, splitId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search by Payload Hash
|
||||
/// </summary>
|
||||
/// <param name="matchType">Match type</param>
|
||||
/// <param name="ecParentId">Payload Hash</param>
|
||||
public class FilterByECParent(FrostFsMatchType matchType, FrostFsObjectId ecParentId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderECParent, ecParentId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search Root objects
|
||||
/// </summary>
|
||||
public class FilterByRootObject() : FrostFsObjectFilter<string>(FrostFsMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates filter to search objects that are physically stored on the server
|
||||
/// </summary
|
||||
public class FilterByPhysicallyStored() : FrostFsObjectFilter<string>(FrostFsMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { }
|
||||
|
90
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs
Normal file
90
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Object;
|
||||
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsObjectHeader(
|
||||
FrostFsContainerId containerId,
|
||||
FrostFsObjectType type = FrostFsObjectType.Regular,
|
||||
FrostFsAttributePair[]? attributes = null,
|
||||
FrostFsSplit? split = null,
|
||||
FrostFsOwner? owner = null,
|
||||
FrostFsVersion? version = null)
|
||||
{
|
||||
private Header? header;
|
||||
private Container.Container.Types.Attribute[]? grpsAttributes;
|
||||
|
||||
public ReadOnlyCollection<FrostFsAttributePair>? Attributes { get; internal set; } =
|
||||
attributes == null ? null :
|
||||
new ReadOnlyCollection<FrostFsAttributePair>(attributes);
|
||||
|
||||
public FrostFsContainerId ContainerId { get; } = containerId;
|
||||
|
||||
public ulong PayloadLength { get; set; }
|
||||
|
||||
public byte[]? PayloadCheckSum { get; set; }
|
||||
|
||||
public FrostFsObjectType ObjectType { get; } = type;
|
||||
|
||||
public FrostFsOwner? OwnerId { get; internal set; } = owner;
|
||||
|
||||
public FrostFsVersion? Version { get; internal set; } = version;
|
||||
|
||||
public FrostFsSplit? Split { get; internal set; } = split;
|
||||
|
||||
internal Container.Container.Types.Attribute[]? GetGrpsAttributes()
|
||||
{
|
||||
grpsAttributes ??= Attributes?
|
||||
.Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value })
|
||||
.ToArray();
|
||||
|
||||
return grpsAttributes;
|
||||
}
|
||||
|
||||
public Header GetHeader()
|
||||
{
|
||||
if (header == null)
|
||||
{
|
||||
var objTypeName = ObjectType switch
|
||||
{
|
||||
FrostFsObjectType.Regular => Object.ObjectType.Regular,
|
||||
FrostFsObjectType.Lock => Object.ObjectType.Lock,
|
||||
FrostFsObjectType.Tombstone => Object.ObjectType.Tombstone,
|
||||
_ => throw new ArgumentException($"Unknown ObjectType. Value: '{ObjectType}'.")
|
||||
};
|
||||
|
||||
this.header = new Header
|
||||
{
|
||||
OwnerId = OwnerId?.ToMessage(),
|
||||
Version = Version?.ToMessage(),
|
||||
ContainerId = ContainerId.ToMessage(),
|
||||
ObjectType = objTypeName,
|
||||
PayloadLength = PayloadLength
|
||||
};
|
||||
|
||||
if (Attributes != null)
|
||||
{
|
||||
foreach (var attribute in Attributes)
|
||||
{
|
||||
this.header.Attributes.Add(attribute.ToMessage());
|
||||
}
|
||||
}
|
||||
|
||||
var split = Split;
|
||||
if (split != null)
|
||||
{
|
||||
this.header.Split = new Header.Types.Split
|
||||
{
|
||||
SplitId = split!.SplitId != null ? split.SplitId.GetSplitId() : null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return this.header;
|
||||
}
|
||||
}
|
33
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs
Normal file
33
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsObjectId(string id)
|
||||
{
|
||||
public string Value { get; } = id;
|
||||
|
||||
public static FrostFsObjectId FromHash(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.");
|
||||
|
||||
return new FrostFsObjectId(Base58.Encode(hash));
|
||||
}
|
||||
|
||||
public byte[] ToHash()
|
||||
{
|
||||
return Base58.Decode(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
38
src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs
Normal file
38
src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsOwner(string id)
|
||||
{
|
||||
private OwnerID? ownerID;
|
||||
|
||||
public string Value { get; } = id;
|
||||
|
||||
public static FrostFsOwner FromKey(ECDsa key)
|
||||
{
|
||||
return new FrostFsOwner(key.PublicKey().PublicKeyToAddress());
|
||||
}
|
||||
|
||||
internal OwnerID OwnerID
|
||||
{
|
||||
get
|
||||
{
|
||||
ownerID ??= this.ToMessage();
|
||||
return ownerID;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ToHash()
|
||||
{
|
||||
return Base58.Decode(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
27
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal file
27
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs
Normal file
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsSplit(SplitId splitId,
|
||||
FrostFsObjectId? previous = null,
|
||||
FrostFsObjectId? parent = null,
|
||||
FrostFsObjectHeader? parentHeader = null,
|
||||
FrostFsSignature? parentSignature = null,
|
||||
ReadOnlyCollection<FrostFsObjectId>? children = null)
|
||||
{
|
||||
public FrostFsSplit() : this(new SplitId())
|
||||
{
|
||||
}
|
||||
|
||||
public SplitId SplitId { get; private set; } = splitId;
|
||||
|
||||
public FrostFsObjectId? Previous { get; } = previous;
|
||||
|
||||
public FrostFsObjectId? Parent { get; } = parent;
|
||||
|
||||
public FrostFsSignature? ParentSignature { get; } = parentSignature;
|
||||
|
||||
public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader;
|
||||
|
||||
public ReadOnlyCollection<FrostFsObjectId>? Children { get; } = children;
|
||||
|
||||
}
|
10
src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public interface IObjectReader : IDisposable
|
||||
{
|
||||
ValueTask<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
|
||||
}
|
62
src/FrostFS.SDK.Client/Models/Object/SplitId.cs
Normal file
62
src/FrostFS.SDK.Client/Models/Object/SplitId.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class SplitId
|
||||
{
|
||||
private readonly Guid id;
|
||||
|
||||
private ByteString? message;
|
||||
|
||||
public SplitId()
|
||||
{
|
||||
this.id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public SplitId(Guid id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private SplitId(byte[] binary)
|
||||
{
|
||||
this.id = new Guid(binary);
|
||||
}
|
||||
|
||||
private SplitId(string str)
|
||||
{
|
||||
this.id = new Guid(str);
|
||||
}
|
||||
|
||||
public static SplitId CreateFromBinary(byte[] binaryData)
|
||||
{
|
||||
return new SplitId(binaryData);
|
||||
}
|
||||
|
||||
public static SplitId CreateFromString(string stringData)
|
||||
{
|
||||
return new SplitId(stringData);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.id.ToString();
|
||||
}
|
||||
|
||||
public byte[]? ToBinary()
|
||||
{
|
||||
if (this.id == Guid.Empty)
|
||||
return null;
|
||||
|
||||
return this.id.ToBytes();
|
||||
}
|
||||
|
||||
public ByteString? GetSplitId()
|
||||
{
|
||||
return this.message ??= ByteString.CopyFrom(ToBinary());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null)
|
||||
{
|
||||
public FrostFsStatusCode Code { get; set; } = code;
|
||||
public string Message { get; set; } = message ?? string.Empty;
|
||||
|
||||
public bool IsSuccess => Code == FrostFsStatusCode.Success;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Response status: {Code}. Message: {Message}.";
|
||||
}
|
||||
}
|
10
src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsSignature()
|
||||
{
|
||||
public byte[]? Key { get; set; }
|
||||
|
||||
public byte[]? Sign { get; set; }
|
||||
|
||||
public SignatureScheme Scheme { get; set; }
|
||||
}
|
20
src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs
Normal file
20
src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class MetaHeader(FrostFsVersion version, int epoch, int ttl)
|
||||
{
|
||||
public FrostFsVersion Version { get; set; } = version;
|
||||
public int Epoch { get; set; } = epoch;
|
||||
public int Ttl { get; set; } = ttl;
|
||||
|
||||
public static MetaHeader Default()
|
||||
{
|
||||
return new MetaHeader(
|
||||
new FrostFsVersion(
|
||||
major: 2,
|
||||
minor: 13
|
||||
),
|
||||
epoch: 0,
|
||||
ttl: 2
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
45
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
45
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class CallContext()
|
||||
{
|
||||
private ByteString? publicKeyCache;
|
||||
|
||||
internal Action<Exception>? PoolErrorHandler { get; set; }
|
||||
|
||||
public ECDsa? Key { get; set; }
|
||||
|
||||
public FrostFsOwner? OwnerId { get; set; }
|
||||
|
||||
public FrostFsVersion? Version { get; set; }
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public TimeSpan Timeout { get; set; }
|
||||
|
||||
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
||||
|
||||
public Action<CallStatistics>? Callback { get; set; }
|
||||
|
||||
public Collection<Interceptor> Interceptors { get; } = [];
|
||||
|
||||
public ByteString? GetPublicKeyCache()
|
||||
{
|
||||
if (publicKeyCache == null && Key != null)
|
||||
{
|
||||
publicKeyCache = ByteString.CopyFrom(Key.PublicKey());
|
||||
}
|
||||
|
||||
return publicKeyCache;
|
||||
}
|
||||
}
|
10
src/FrostFS.SDK.Client/Parameters/Credentials.cs
Normal file
10
src/FrostFS.SDK.Client/Parameters/Credentials.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class Credentials(ECDsa key, FrostFsOwner ownerId)
|
||||
{
|
||||
public ECDsa Key { get; } = key;
|
||||
|
||||
public FrostFsOwner OwnerId { get; } = ownerId;
|
||||
}
|
11
src/FrostFS.SDK.Client/Parameters/IContext.cs
Normal file
11
src/FrostFS.SDK.Client/Parameters/IContext.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public interface IContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The method call can be extended with additional behavior like canceling by timeout or user's request,
|
||||
/// callbacks, interceptors.
|
||||
/// </summary>
|
||||
/// <value>Additional parameters for calling the method</value>
|
||||
CallContext? Context { get; }
|
||||
}
|
12
src/FrostFS.SDK.Client/Parameters/ISessionToken.cs
Normal file
12
src/FrostFS.SDK.Client/Parameters/ISessionToken.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public interface ISessionToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Object represents token of the FrostFS Object session. A session is opened between any two sides of the
|
||||
/// system, and implements a mechanism for transferring the power of attorney of actions to another network
|
||||
/// member. The session has a limited validity period, and applies to a strictly defined set of operations.
|
||||
/// </summary>
|
||||
/// <value>Instance of the session obtained from the server</value>
|
||||
FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
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;
|
||||
}
|
8
src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
Normal file
8
src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
public FrostFsChainTarget Target { get; } = target;
|
||||
|
||||
public FrostFsChain Chain { get; } = chain;
|
||||
}
|
8
src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs
Normal file
8
src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx)
|
||||
{
|
||||
public FrostFsChainTarget Target { get; } = target;
|
||||
|
||||
public FrostFsChain Chain { get; } = chain;
|
||||
}
|
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)
|
||||
{
|
||||
}
|
14
src/FrostFS.SDK.Client/Parameters/PrmBase.cs
Normal file
14
src/FrostFS.SDK.Client/Parameters/PrmBase.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Specialized;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext
|
||||
{
|
||||
/// <summary>
|
||||
/// FrostFS request X-Headers
|
||||
/// </summary>
|
||||
public NameValueCollection XHeaders { get; } = xheaders ?? [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public CallContext Context { get; } = ctx ?? new CallContext();
|
||||
}
|
17
src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs
Normal file
17
src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerInfo Container { get; set; } = container;
|
||||
|
||||
/// <summary>
|
||||
/// Since the container becomes available with some delay, it needs to poll the container status
|
||||
/// </summary>
|
||||
/// <value>Rules for polling the result</value>
|
||||
public PrmWait? WaitParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Blank session token
|
||||
/// </summary>
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
14
src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs
Normal file
14
src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||
|
||||
/// <summary>
|
||||
/// Since the container is removed with some delay, it needs to poll the container status
|
||||
/// </summary>
|
||||
/// <value>Rules for polling the result</value>
|
||||
public PrmWait? WaitParams { get; set; }
|
||||
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
6
src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs
Normal file
6
src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
11
src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs
Normal file
11
src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||
|
||||
public FrostFsObjectId ObjectId { get; set; } = objectId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
11
src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs
Normal file
11
src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||
|
||||
public FrostFsObjectId ObjectId { get; set; } = objectId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
11
src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs
Normal file
11
src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||
|
||||
public FrostFsObjectId ObjectId { get; set; } = objectId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
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; }
|
||||
}
|
46
src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs
Normal file
46
src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.IO;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
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.
|
||||
/// Optional parameters ike <c>Attributes</c> can be provided as well.
|
||||
/// </summary>
|
||||
/// <value>Header with required parameters to create an object</value>
|
||||
public FrostFsObjectHeader? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A stream with source data
|
||||
/// </summary>
|
||||
public Stream? Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object size is limited. In the data exceeds the limit, the object will be splitted.
|
||||
/// If the parameter is <c>true</c>, the client side cut is applied. Otherwise, the data is transferred
|
||||
/// as a stream and will be cut on server side.
|
||||
/// </summary>
|
||||
/// <value>Is client cut is applied</value>
|
||||
public bool ClientCut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides default size of the buffer for stream transferring.
|
||||
/// </summary>
|
||||
/// <value>Size of the buffer</value>
|
||||
public int BufferMaxSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows to define a buffer for chunks to manage by the memory allocation and releasing.
|
||||
/// </summary>
|
||||
public byte[]? CustomBuffer { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
|
||||
internal int MaxObjectSizeCache { get; set; }
|
||||
|
||||
internal ulong CurrentStreamPosition { get; set; }
|
||||
|
||||
internal ulong FullLength { get; set; }
|
||||
}
|
21
src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs
Normal file
21
src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines container for the search
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public FrostFsContainerId ContainerId { get; set; } = containerId;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the search criteria
|
||||
/// </summary>
|
||||
/// <value>Collection of filters</value>
|
||||
public IEnumerable<IObjectFilter> Filters { get; set; } = filters;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
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;
|
||||
}
|
9
src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs
Normal file
9
src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsObject FrostFsObject { get; set; } = frostFsObject;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
24
src/FrostFS.SDK.Client/Parameters/PrmWait.cs
Normal file
24
src/FrostFS.SDK.Client/Parameters/PrmWait.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class PrmWait(TimeSpan timeout, TimeSpan pollInterval)
|
||||
{
|
||||
private static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(120);
|
||||
private static TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(5);
|
||||
|
||||
public PrmWait(int timeout, int interval) : this(TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(interval))
|
||||
{
|
||||
}
|
||||
|
||||
public static PrmWait DefaultParams { get; } = new PrmWait(DefaultTimeout, DefaultPollInterval);
|
||||
|
||||
public TimeSpan Timeout { get; set; } = timeout.Ticks == 0 ? DefaultTimeout : timeout;
|
||||
|
||||
public TimeSpan PollInterval { get; set; } = pollInterval.Ticks == 0 ? DefaultPollInterval : pollInterval;
|
||||
|
||||
public DateTime GetDeadline()
|
||||
{
|
||||
return DateTime.UtcNow.AddTicks(Timeout.Ticks);
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
157
src/FrostFS.SDK.Client/Pool/ClientWrapper.cs
Normal file
157
src/FrostFS.SDK.Client/Pool/ClientWrapper.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
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);
|
||||
|
||||
var dialCtx = new CallContext
|
||||
{
|
||||
Timeout = TimeSpan.FromTicks((long)WrapperPrm.DialTimeout),
|
||||
CancellationToken = ctx.CancellationToken
|
||||
};
|
||||
|
||||
var error = await client.Dial(ctx).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
SetUnhealthyOnDial();
|
||||
return wasHealthy;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
client = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
client?.Dispose();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var prmNodeInfo = new PrmNodeInfo(ctx);
|
||||
var res = await Client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
||||
}
|
||||
catch (FrostFsException)
|
||||
{
|
||||
SetUnhealthy();
|
||||
return wasHealthy;
|
||||
}
|
||||
|
||||
SetHealthy();
|
||||
return !wasHealthy;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
36
src/FrostFS.SDK.Client/Pool/InitParameters.cs
Normal file
36
src/FrostFS.SDK.Client/Pool/InitParameters.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using Grpc.Net.Client;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
// InitParameters contains values used to initialize connection Pool.
|
||||
public class InitParameters
|
||||
{
|
||||
public ECDsa? Key { get; set; }
|
||||
|
||||
public ulong NodeDialTimeout { get; set; }
|
||||
|
||||
public ulong NodeStreamTimeout { get; set; }
|
||||
|
||||
public ulong HealthcheckTimeout { get; set; }
|
||||
|
||||
public ulong ClientRebalanceInterval { get; set; }
|
||||
|
||||
public ulong SessionExpirationDuration { get; set; }
|
||||
|
||||
public uint ErrorThreshold { get; set; }
|
||||
|
||||
public NodeParam[]? NodeParams { get; set; }
|
||||
|
||||
public GrpcChannelOptions[]? DialOptions { get; set; }
|
||||
|
||||
public Func<string, ClientWrapper>? ClientBuilder { get; set; }
|
||||
|
||||
public ulong GracefulCloseOnSwitchTimeout { get; set; }
|
||||
|
||||
public ILogger? Logger { get; set; }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue