Compare commits
13 commits
feature/ci
...
master
Author | SHA1 | Date | |
---|---|---|---|
43e300c773 | |||
568bdc67e8 | |||
8637515869 | |||
db9b93b2e6 | |||
543247e4d9 | |||
abd9b5d0d0 | |||
c9418a1894 | |||
9bb7b5eff8 | |||
749000a090 | |||
766f61a5f7 | |||
c406df1a78 | |||
14dc76898e | |||
003b7fdfdd |
304 changed files with 10764 additions and 4474 deletions
|
@ -214,7 +214,7 @@ dotnet_diagnostic.CA1700.severity = warning
|
|||
dotnet_diagnostic.CA1707.severity = warning
|
||||
|
||||
# CA1708: Identifiers should differ by more than case
|
||||
dotnet_diagnostic.CA1708.severity = warning
|
||||
dotnet_diagnostic.CA1708.severity = none
|
||||
|
||||
# CA1710: Identifiers should have correct suffix
|
||||
dotnet_diagnostic.CA1710.severity = warning
|
||||
|
@ -232,7 +232,7 @@ dotnet_diagnostic.CA1713.severity = warning
|
|||
dotnet_diagnostic.CA1715.severity = warning
|
||||
|
||||
# CA1716: Identifiers should not match keywords
|
||||
dotnet_diagnostic.CA1716.severity = warning
|
||||
dotnet_diagnostic.CA1716.severity = none
|
||||
|
||||
# CA1720: Identifier contains type name
|
||||
dotnet_diagnostic.CA1720.severity = warning
|
||||
|
|
22
.forgejo/workflows/lint-build.yml
Normal file
22
.forgejo/workflows/lint-build.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: lint-build
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-build:
|
||||
name: dotnet${{ matrix.dotnet }}
|
||||
runs-on: docker
|
||||
container: git.frostfs.info/truecloudlab/env:dotnet-${{ matrix.dotnet }}
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet:
|
||||
- '8.0'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Dotnet runs code analyzers on build (if configured):
|
||||
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?tabs=net-8#enable-on-build
|
||||
- run: dotnet build
|
3
CODEOWNERS
Normal file
3
CODEOWNERS
Normal file
|
@ -0,0 +1,3 @@
|
|||
.* @PavelGrossSpb
|
||||
.forgejo/.* @potyarkin
|
||||
Makefile @potyarkin
|
|
@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Client", "src\FrostFS.SDK.Client\FrostFS.SDK.Client.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Protos", "src\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}"
|
||||
EndProject
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
internal static class Caches
|
||||
{
|
|
@ -4,11 +4,15 @@ using FrostFS.SDK.Cryptography;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class ClientKey(ECDsa key)
|
||||
{
|
||||
internal ECDsa ECDsaKey { get; } = key;
|
||||
|
||||
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
|
||||
|
||||
internal string PublicKey { get; } = key.PublicKey().ToString();
|
||||
|
||||
internal FrostFsOwner Owner { get; } = new FrostFsOwner(key.PublicKey().PublicKeyToAddress());
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsException : Exception
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsInvalidObjectException : FrostFsException
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsResponseException : FrostFsException
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFsStreamException : FrostFsException
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class SessionExpiredException : FrostFsException
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class SessionNotFoundException : FrostFsException
|
||||
{
|
|
@ -4,16 +4,19 @@ using Google.Protobuf;
|
|||
|
||||
namespace FrostFS.SDK.Cryptography;
|
||||
|
||||
public static class UUIDExtension
|
||||
public static class FrostFsExtensions
|
||||
{
|
||||
public static ByteString Sha256(this IMessage data)
|
||||
{
|
||||
return ByteString.CopyFrom(data.ToByteArray().Sha256());
|
||||
}
|
||||
|
||||
public static Guid ToUuid(this ByteString id)
|
||||
{
|
||||
if (id == null)
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
|
||||
var bytes = id.ToByteArray();
|
||||
|
||||
var orderedBytes = GetGuidBytesDirectOrder(bytes);
|
||||
var orderedBytes = GetGuidBytesDirectOrder(id.Span);
|
||||
|
||||
return new Guid(orderedBytes);
|
||||
}
|
||||
|
@ -32,7 +35,7 @@ public static class UUIDExtension
|
|||
return orderedBytes;
|
||||
}
|
||||
|
||||
private static byte[] GetGuidBytesDirectOrder(byte[] source)
|
||||
private static byte[] GetGuidBytesDirectOrder(ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (source.Length != 16)
|
||||
throw new ArgumentException("Wrong uuid binary format");
|
|
@ -14,23 +14,31 @@
|
|||
<PropertyGroup>
|
||||
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_SkipUpgradeNetAnalyzersNuGetWarning>true</_SkipUpgradeNetAnalyzersNuGetWarning>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.4">
|
||||
<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" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="7.0.0" />
|
||||
<PackageReference Include="System.Runtime.Caching" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj" />
|
||||
<ProjectReference Include="..\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
473
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
473
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
|
@ -0,0 +1,473 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Frostfs.V2.Ape;
|
||||
|
||||
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 Microsoft.Extensions.Options;
|
||||
|
||||
using static Frostfs.V2.Apemanager.APEManagerService;
|
||||
using static FrostFS.Accounting.AccountingService;
|
||||
using static FrostFS.Container.ContainerService;
|
||||
using static FrostFS.Netmap.NetmapService;
|
||||
using static FrostFS.Object.ObjectService;
|
||||
using static FrostFS.Session.SessionService;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public class FrostFSClient : IFrostFSClient
|
||||
{
|
||||
internal ContainerServiceClient? ContainerServiceClient { get; set; }
|
||||
internal ContainerServiceProvider? ContainerServiceProvider { get; set; }
|
||||
|
||||
internal NetmapServiceClient? NetmapServiceClient { get; set; }
|
||||
internal NetmapServiceProvider? NetmapServiceProvider { get; set; }
|
||||
|
||||
internal APEManagerServiceClient? ApeManagerServiceClient { get; set; }
|
||||
internal ApeManagerServiceProvider? ApeManagerServiceProvider { get; set; }
|
||||
|
||||
internal SessionServiceClient? SessionServiceClient { get; set; }
|
||||
internal SessionServiceProvider? SessionServiceProvider { get; set; }
|
||||
|
||||
internal ObjectServiceClient? ObjectServiceClient { get; set; }
|
||||
internal ObjectServiceProvider? ObjectServiceProvider { get; set; }
|
||||
|
||||
internal AccountingServiceClient? AccountingServiceClient { get; set; }
|
||||
internal AccountingServiceProvider? AccountingServiceProvider { get; set; }
|
||||
|
||||
internal ClientContext ClientCtx { get; set; }
|
||||
|
||||
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, Func<string, ChannelBase> grpcChannelFactory)
|
||||
{
|
||||
if (clientOptions is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientOptions));
|
||||
}
|
||||
|
||||
if (grpcChannelFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(grpcChannelFactory));
|
||||
}
|
||||
|
||||
return new FrostFSClient(clientOptions, grpcChannelFactory);
|
||||
}
|
||||
|
||||
/// <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<ClientSettings> settings,
|
||||
Func<string, ChannelBase> grpcChannelFactory,
|
||||
NetmapServiceClient netmapService,
|
||||
SessionServiceClient sessionService,
|
||||
ContainerServiceClient containerService,
|
||||
ObjectServiceClient objectService)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
if (grpcChannelFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(grpcChannelFactory));
|
||||
}
|
||||
|
||||
if (netmapService is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(netmapService));
|
||||
}
|
||||
|
||||
if (sessionService is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sessionService));
|
||||
}
|
||||
|
||||
if (containerService is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(containerService));
|
||||
}
|
||||
|
||||
if (objectService is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectService));
|
||||
}
|
||||
|
||||
return new FrostFSClient(
|
||||
settings, channel: grpcChannelFactory(settings.Value.Host), containerService, netmapService, sessionService, objectService);
|
||||
}
|
||||
|
||||
private FrostFSClient(
|
||||
IOptions<ClientSettings> settings,
|
||||
ChannelBase channel,
|
||||
ContainerServiceClient containerService,
|
||||
NetmapServiceClient netmapService,
|
||||
SessionServiceClient sessionService,
|
||||
ObjectServiceClient objectService)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
var ecdsaKey = settings.Value.Key.LoadWif();
|
||||
|
||||
ClientCtx = new ClientContext(
|
||||
client: this,
|
||||
key: new ClientKey(ecdsaKey),
|
||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||
channel: channel,
|
||||
version: new FrostFsVersion(2, 13))
|
||||
{
|
||||
SessionCache = new SessionCache(0),
|
||||
Callback = settings.Value.Callback,
|
||||
Interceptors = settings.Value.Interceptors
|
||||
};
|
||||
|
||||
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> settings, Func<string, ChannelBase> grpcChannelFactory)
|
||||
{
|
||||
var clientSettings = (settings?.Value) ?? throw new ArgumentNullException(nameof(settings), "Options value must be initialized");
|
||||
|
||||
clientSettings.Validate();
|
||||
|
||||
var ecdsaKey = clientSettings.Key.LoadWif();
|
||||
|
||||
ClientCtx = new ClientContext(
|
||||
this,
|
||||
key: new ClientKey(ecdsaKey),
|
||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||
channel: grpcChannelFactory(settings.Value.Host),
|
||||
version: new FrostFsVersion(2, 13))
|
||||
{
|
||||
SessionCache = new SessionCache(0),
|
||||
Callback = settings.Value.Callback,
|
||||
Interceptors = settings.Value.Interceptors
|
||||
};
|
||||
|
||||
// TODO: define timeout logic
|
||||
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
||||
}
|
||||
|
||||
internal FrostFSClient(WrapperPrm prm, SessionCache cache)
|
||||
{
|
||||
ClientCtx = new ClientContext(
|
||||
client: this,
|
||||
key: new ClientKey(prm.Key),
|
||||
owner: FrostFsOwner.FromKey(prm.Key!),
|
||||
channel: prm.GrpcChannelFactory(prm.Address),
|
||||
version: new FrostFsVersion(2, 13))
|
||||
{
|
||||
SessionCache = cache,
|
||||
Interceptors = prm.Interceptors,
|
||||
Callback = prm.Callback
|
||||
};
|
||||
}
|
||||
|
||||
#region ApeManagerImplementation
|
||||
public Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args, CallContext ctx)
|
||||
{
|
||||
return GetApeManagerService().AddChainAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx)
|
||||
{
|
||||
return GetApeManagerService().RemoveChainAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<Chain[]> ListChainAsync(PrmApeChainList args, CallContext ctx)
|
||||
{
|
||||
return GetApeManagerService().ListChainAsync(args, ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContainerImplementation
|
||||
public Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx)
|
||||
{
|
||||
return GetContainerService().GetContainerAsync(args, ctx);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args, CallContext ctx)
|
||||
{
|
||||
return GetContainerService().ListContainersAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args, CallContext ctx)
|
||||
{
|
||||
return GetContainerService().CreateContainerAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx)
|
||||
{
|
||||
return GetContainerService().DeleteContainerAsync(args, ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region NetworkImplementation
|
||||
public Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(CallContext ctx)
|
||||
{
|
||||
return GetNetmapService().GetNetmapSnapshotAsync(ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsNodeInfo> GetNodeInfoAsync(CallContext ctx)
|
||||
{
|
||||
return GetNetmapService().GetLocalNodeInfoAsync(ctx);
|
||||
}
|
||||
|
||||
public Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
|
||||
{
|
||||
return GetNetmapService().GetNetworkSettingsAsync(ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ObjectImplementation
|
||||
public Task<FrostFsHeaderResult> GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().GetObjectHeadAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsObject> GetObjectAsync(PrmObjectGet args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().GetObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<RangeReader> GetRangeAsync(PrmRangeGet args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().GetRangeAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<ReadOnlyMemory<byte>[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().GetRangeHashAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<IObjectWriter> PutObjectAsync(PrmObjectPut args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().PutStreamObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().PutClientCutObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().PutSingleObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().PatchObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().DeleteObjectAsync(args, ctx);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args, CallContext ctx)
|
||||
{
|
||||
return GetObjectService().SearchObjectsAsync(args, ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Session Implementation
|
||||
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args, CallContext ctx)
|
||||
{
|
||||
var token = await CreateSessionInternalAsync(args, ctx).ConfigureAwait(false);
|
||||
|
||||
return new FrostFsSessionToken(token);
|
||||
}
|
||||
|
||||
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args, CallContext ctx)
|
||||
{
|
||||
var service = GetSessionService();
|
||||
return service.CreateSessionAsync(args, ctx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Accounting Implementation
|
||||
public async Task<Accounting.Decimal> GetBalanceAsync(CallContext ctx)
|
||||
{
|
||||
return await GetAccouningService().GetBallance(ctx).ConfigureAwait(false);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async void CheckFrostFsVersionSupport(CallContext ctx)
|
||||
{
|
||||
var service = GetNetmapService();
|
||||
var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx).ConfigureAwait(false);
|
||||
|
||||
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
|
||||
{
|
||||
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
|
||||
throw new FrostFsException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private CallInvoker? CreateInvoker()
|
||||
{
|
||||
CallInvoker? callInvoker = null;
|
||||
|
||||
if (ClientCtx.Interceptors != null)
|
||||
{
|
||||
foreach (var interceptor in ClientCtx.Interceptors)
|
||||
callInvoker = AddInvoker(callInvoker, interceptor);
|
||||
}
|
||||
|
||||
if (ClientCtx.Callback != null)
|
||||
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ClientCtx.Callback));
|
||||
|
||||
if (ClientCtx.PoolErrorHandler != null)
|
||||
callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ClientCtx.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()
|
||||
{
|
||||
if (NetmapServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
NetmapServiceClient = NetmapServiceClient ?? (
|
||||
invoker != null
|
||||
? new NetmapServiceClient(invoker)
|
||||
: new NetmapServiceClient(ClientCtx.Channel));
|
||||
|
||||
NetmapServiceProvider = new NetmapServiceProvider(NetmapServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return NetmapServiceProvider;
|
||||
}
|
||||
|
||||
private SessionServiceProvider GetSessionService()
|
||||
{
|
||||
if (SessionServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
SessionServiceClient = SessionServiceClient ?? (
|
||||
invoker != null
|
||||
? new SessionServiceClient(invoker)
|
||||
: new SessionServiceClient(ClientCtx.Channel));
|
||||
|
||||
SessionServiceProvider = new SessionServiceProvider(SessionServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return SessionServiceProvider;
|
||||
|
||||
}
|
||||
|
||||
private ApeManagerServiceProvider GetApeManagerService()
|
||||
{
|
||||
if (ApeManagerServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
ApeManagerServiceClient = ApeManagerServiceClient ?? (
|
||||
invoker != null
|
||||
? new APEManagerServiceClient(invoker)
|
||||
: new APEManagerServiceClient(ClientCtx.Channel));
|
||||
|
||||
ApeManagerServiceProvider = new ApeManagerServiceProvider(ApeManagerServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return ApeManagerServiceProvider;
|
||||
}
|
||||
|
||||
private AccountingServiceProvider GetAccouningService()
|
||||
{
|
||||
if (this.AccountingServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
AccountingServiceClient = AccountingServiceClient ?? (
|
||||
invoker != null
|
||||
? new AccountingServiceClient(invoker)
|
||||
: new AccountingServiceClient(ClientCtx.Channel));
|
||||
|
||||
AccountingServiceProvider = new AccountingServiceProvider(AccountingServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return AccountingServiceProvider;
|
||||
}
|
||||
|
||||
private ContainerServiceProvider GetContainerService()
|
||||
{
|
||||
if (this.ContainerServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
ContainerServiceClient = ContainerServiceClient ?? (
|
||||
invoker != null
|
||||
? new ContainerServiceClient(invoker)
|
||||
: new ContainerServiceClient(ClientCtx.Channel));
|
||||
|
||||
ContainerServiceProvider = new ContainerServiceProvider(ContainerServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return ContainerServiceProvider;
|
||||
}
|
||||
|
||||
private ObjectServiceProvider GetObjectService()
|
||||
{
|
||||
if (this.ObjectServiceProvider == null)
|
||||
{
|
||||
var invoker = CreateInvoker();
|
||||
|
||||
ObjectServiceClient = ObjectServiceClient ?? (
|
||||
invoker != null
|
||||
? new ObjectServiceClient(invoker)
|
||||
: new ObjectServiceClient(ClientCtx.Channel));
|
||||
|
||||
ObjectServiceProvider = new ObjectServiceProvider(ObjectServiceClient, ClientCtx);
|
||||
}
|
||||
|
||||
return ObjectServiceProvider;
|
||||
}
|
||||
|
||||
public async Task<string?> Dial(CallContext ctx)
|
||||
{
|
||||
var service = GetAccouningService();
|
||||
_ = await service.GetBallance(ctx).ConfigureAwait(false);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool RestartIfUnhealthy(CallContext ctx)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "<Pending>", Scope = "member", Target = "~M:FrostFS.SDK.ClientV2.Sampler.Next~System.Int32")]
|
||||
[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "<Pending>", Scope = "member", Target = "~M:FrostFS.SDK.Client.Sampler.Next~System.Int32")]
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
|
||||
Justification = "parameters are provided by GRPC infrastructure")]
|
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
|
||||
Justification = "parameters are provided by GRPC infrastructure")]
|
68
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
68
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Frostfs.V2.Ape;
|
||||
|
||||
namespace FrostFS.SDK.Client.Interfaces;
|
||||
|
||||
public interface IFrostFSClient
|
||||
{
|
||||
#region Network
|
||||
Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(CallContext ctx);
|
||||
|
||||
Task<FrostFsNodeInfo> GetNodeInfoAsync(CallContext ctx);
|
||||
|
||||
Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx);
|
||||
#endregion
|
||||
|
||||
#region Session
|
||||
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args, CallContext ctx);
|
||||
#endregion
|
||||
|
||||
#region ApeManager
|
||||
Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args, CallContext ctx);
|
||||
|
||||
Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx);
|
||||
|
||||
Task<Chain[]> ListChainAsync(PrmApeChainList args, CallContext ctx);
|
||||
#endregion
|
||||
|
||||
#region Container
|
||||
Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx);
|
||||
|
||||
IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args, CallContext ctx);
|
||||
|
||||
Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args, CallContext ctx);
|
||||
|
||||
Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx);
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
Task<FrostFsHeaderResult> GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx);
|
||||
|
||||
Task<FrostFsObject> GetObjectAsync(PrmObjectGet args, CallContext ctx);
|
||||
|
||||
Task<RangeReader> GetRangeAsync(PrmRangeGet args, CallContext ctx);
|
||||
|
||||
Task<ReadOnlyMemory<byte>[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx);
|
||||
|
||||
Task<IObjectWriter> PutObjectAsync(PrmObjectPut args, CallContext ctx);
|
||||
|
||||
Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx);
|
||||
|
||||
Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx);
|
||||
|
||||
Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args, CallContext ctx);
|
||||
|
||||
Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx);
|
||||
|
||||
IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args, CallContext ctx);
|
||||
#endregion
|
||||
|
||||
#region Account
|
||||
Task<Accounting.Decimal> GetBalanceAsync(CallContext ctx);
|
||||
#endregion
|
||||
|
||||
public Task<string?> Dial(CallContext ctx);
|
||||
}
|
12
src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs
Normal file
12
src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FrostFS.SDK.Client.Interfaces
|
||||
{
|
||||
public interface IObjectWriter : IDisposable
|
||||
{
|
||||
Task WriteAsync(ReadOnlyMemory<byte> memory);
|
||||
|
||||
Task<FrostFsObjectId> CompleteAsync();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
internal static partial class FrostFsMessages
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ContainerMapper
|
||||
{
|
|
@ -7,7 +7,7 @@ using Google.Protobuf;
|
|||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ContainerIdMapper
|
||||
{
|
||||
|
@ -33,7 +33,16 @@ public static class ContainerIdMapper
|
|||
|
||||
Caches.Containers.Set(containerId, message, _oneHourExpiration);
|
||||
}
|
||||
|
||||
return message!;
|
||||
}
|
||||
|
||||
public static FrostFsContainerId ToModel(this ContainerID message)
|
||||
{
|
||||
if (message is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
return new FrostFsContainerId(Base58.Encode(message.Value.Span));
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using FrostFS.Session;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class MetaHeaderMapper
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class NetmapMapper
|
||||
{
|
|
@ -2,9 +2,9 @@ using System;
|
|||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class NodeInfoMapper
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ public static class NodeInfoMapper
|
|||
state: state,
|
||||
addresses: [.. nodeInfo.Addresses],
|
||||
attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value),
|
||||
publicKey: nodeInfo.PublicKey.ToByteArray()
|
||||
publicKey: nodeInfo.PublicKey.Memory
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class PlacementPolicyMapper
|
||||
{
|
||||
|
@ -16,6 +16,9 @@ public static class PlacementPolicyMapper
|
|||
|
||||
return new FrostFsPlacementPolicy(
|
||||
placementPolicy.Unique,
|
||||
placementPolicy.ContainerBackupFactor,
|
||||
new System.Collections.ObjectModel.Collection<FrostFsSelector>(placementPolicy.Selectors.Select(selector => selector.ToModel()).ToList()),
|
||||
new System.Collections.ObjectModel.Collection<FrostFsFilter>(placementPolicy.Filters.Select(filter => filter.ToModel()).ToList()),
|
||||
placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||
);
|
||||
}
|
91
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
91
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class PolicyMapper
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public static Selector ToMessage(this FrostFsSelector selector)
|
||||
{
|
||||
if (selector is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selector));
|
||||
}
|
||||
|
||||
return new Selector
|
||||
{
|
||||
Name = selector.Name,
|
||||
Count = selector.Count,
|
||||
Clause = (Clause)selector.Clause,
|
||||
Attribute = selector.Attribute,
|
||||
Filter = selector.Filter
|
||||
};
|
||||
}
|
||||
|
||||
public static FrostFsSelector ToModel(this Selector selector)
|
||||
{
|
||||
if (selector is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selector));
|
||||
}
|
||||
|
||||
return new FrostFsSelector(selector.Name)
|
||||
{
|
||||
Count = selector.Count,
|
||||
Clause = (int)selector.Clause,
|
||||
Attribute = selector.Attribute,
|
||||
Filter = selector.Filter
|
||||
};
|
||||
}
|
||||
|
||||
public static Filter ToMessage(this FrostFsFilter filter)
|
||||
{
|
||||
if (filter is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
|
||||
var message = new Filter
|
||||
{
|
||||
Name = filter.Name,
|
||||
Key = filter.Key,
|
||||
Op = (Operation)filter.Operation,
|
||||
Value = filter.Value,
|
||||
};
|
||||
|
||||
message.Filters.AddRange(filter.Filters.Select(f => f.ToMessage()));
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static FrostFsFilter ToModel(this Filter filter)
|
||||
{
|
||||
if (filter is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
|
||||
return new FrostFsFilter(filter.Name, filter.Key, (int)filter.Op, filter.Value, filter.Filters.Select(f => f.ToModel()).ToArray());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
internal static class ObjectMapper
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ internal static class ObjectMapper
|
|||
{
|
||||
return new FrostFsObject(obj.Header.ToModel())
|
||||
{
|
||||
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray())
|
||||
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.Span)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using FrostFS.Object;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectAttributeMapper
|
||||
{
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using FrostFS.Object;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectFilterMapper
|
||||
{
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using FrostFS.Object;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectHeaderMapper
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ public static class ObjectHeaderMapper
|
|||
}
|
||||
|
||||
var model = new FrostFsObjectHeader(
|
||||
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())),
|
||||
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)),
|
||||
objTypeName,
|
||||
header.Attributes.Select(attribute => attribute.ToModel()).ToArray(),
|
||||
split,
|
|
@ -4,7 +4,7 @@ using FrostFS.Refs;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class ObjectIdMapper
|
||||
{
|
||||
|
@ -28,6 +28,6 @@ public static class ObjectIdMapper
|
|||
throw new ArgumentNullException(nameof(objectId));
|
||||
}
|
||||
|
||||
return FrostFsObjectId.FromHash(objectId.Value.ToByteArray());
|
||||
return FrostFsObjectId.FromHash(objectId.Value.Span);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ using Google.Protobuf;
|
|||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class OwnerIdMapper
|
||||
{
|
||||
|
@ -44,7 +44,7 @@ public static class OwnerIdMapper
|
|||
|
||||
if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model))
|
||||
{
|
||||
model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray()));
|
||||
model = new FrostFsOwner(Base58.Encode(message.Value.Span));
|
||||
|
||||
Caches.Owners.Set(message, model, _oneHourExpiration);
|
||||
}
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public static class SessionMapper
|
||||
{
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class SignatureMapper
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class StatusMapper
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Threading;
|
|||
|
||||
using FrostFS.Refs;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
public static class VersionMapper
|
||||
{
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
using Frostfs.V2.Ape;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable<FrostFsChainTarget>
|
||||
{
|
||||
|
@ -36,13 +36,19 @@ public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatab
|
|||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
var target = (FrostFsChainTarget)obj;
|
||||
return Equals(target);
|
||||
if (obj == null || obj is not FrostFsChainTarget)
|
||||
return false;
|
||||
|
||||
return Equals((FrostFsChainTarget)obj);
|
||||
}
|
||||
public readonly bool Equals(FrostFsChainTarget other)
|
||||
{
|
||||
return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return $"{Name}{Type}".GetHashCode();
|
||||
return Name.GetHashCode() ^ (int)Type;
|
||||
}
|
||||
|
||||
public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
|
||||
|
@ -54,9 +60,4 @@ public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatab
|
|||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public readonly bool Equals(FrostFsChainTarget other)
|
||||
{
|
||||
return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public enum FrostFsTargetType
|
||||
{
|
37
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
37
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class ClientSettings
|
||||
{
|
||||
protected static readonly string errorTemplate = "{0} is required parameter";
|
||||
|
||||
public string Host { get; set; } = string.Empty;
|
||||
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
public Action<CallStatistics>? Callback { get; set; }
|
||||
|
||||
public Collection<Interceptor> Interceptors { get; } = [];
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
StringBuilder? errors = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Host))
|
||||
(errors = new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host)));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Key))
|
||||
(errors ??= new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key)));
|
||||
|
||||
if (errors != null)
|
||||
{
|
||||
throw new ArgumentException(errors.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
@ -27,7 +27,7 @@ public class FrostFsContainerId
|
|||
|
||||
if (containerID != null)
|
||||
{
|
||||
this.modelId = Base58.Encode(containerID.Value.ToByteArray());
|
||||
this.modelId = Base58.Encode(containerID.Value.Span);
|
||||
return this.modelId;
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ using System;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
@ -96,7 +96,7 @@ public class FrostFsContainerInfo
|
|||
PlacementPolicy = PlacementPolicy.Value.GetPolicy(),
|
||||
Nonce = ByteString.CopyFrom(Nonce.ToBytes()),
|
||||
OwnerId = Owner?.OwnerID,
|
||||
Version = Version?.Version
|
||||
Version = Version?.VersionID
|
||||
};
|
||||
|
||||
var attribs = GetGrpsAttributes();
|
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsFilter(string name, string key, int operation, string value, FrostFsFilter[] filters) : IFrostFsFilter
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public string Key { get; } = key;
|
||||
public int Operation { get; } = operation;
|
||||
public string Value { get; } = value;
|
||||
public FrostFsFilter[] Filters { get; } = filters;
|
||||
}
|
246
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
246
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
|
@ -0,0 +1,246 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
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;
|
||||
|
||||
internal static INormalizer NewReverseMinNorm(double minV)
|
||||
{
|
||||
return new ReverseMinNorm { min = minV };
|
||||
}
|
||||
|
||||
// newSigmoidNorm returns a normalizer which
|
||||
// normalize values in range of 0.0 to 1.0 to a scaled sigmoid.
|
||||
internal static INormalizer NewSigmoidNorm(double scale)
|
||||
{
|
||||
return new SigmoidNorm(scale);
|
||||
}
|
||||
|
||||
// PlacementVectors sorts container nodes returned by ContainerNodes method
|
||||
// and returns placement vectors for the entity identified by the given pivot.
|
||||
// For example, in order to build node list to store the object, binary-encoded
|
||||
// object identifier can be used as pivot. Result is deterministic for
|
||||
// the fixed NetMap and parameters.
|
||||
public FrostFsNodeInfo[][] PlacementVectors(FrostFsNodeInfo[][] vectors, byte[] pivot)
|
||||
{
|
||||
if (vectors is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(vectors));
|
||||
}
|
||||
|
||||
using var murmur3 = new Murmur3(0);
|
||||
var hash = murmur3.GetCheckSum64(pivot);
|
||||
|
||||
var wf = Tools.DefaultWeightFunc(NodeInfoCollection.ToArray());
|
||||
|
||||
var result = new FrostFsNodeInfo[vectors.Length][];
|
||||
var maxSize = vectors.Max(x => x.Length);
|
||||
|
||||
var spanWeigths = new double[maxSize];
|
||||
|
||||
for (int i = 0; i < vectors.Length; i++)
|
||||
{
|
||||
result[i] = new FrostFsNodeInfo[vectors[i].Length];
|
||||
|
||||
for (int j = 0; j < vectors[i].Length; j++)
|
||||
{
|
||||
result[i][j] = vectors[i][j];
|
||||
}
|
||||
|
||||
Tools.AppendWeightsTo(result[i], wf, ref spanWeigths);
|
||||
|
||||
result[i] = Tools.SortHasherSliceByWeightValue(result[i].ToList<FrostFsNodeInfo>(), spanWeigths, hash).ToArray();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// SelectFilterNodes returns a two-dimensional list of nodes as a result of applying the
|
||||
// given SelectFilterExpr to the NetMap.
|
||||
// If the SelectFilterExpr contains only filters, the result contains a single row with the
|
||||
// result of the last filter application.
|
||||
// If the SelectFilterExpr contains only selectors, the result contains the selection rows
|
||||
// of the last select application.
|
||||
List<List<FrostFsNodeInfo>> SelectFilterNodes(SelectFilterExpr expr)
|
||||
{
|
||||
var policy = new FrostFsPlacementPolicy(false, expr.Cbf, [expr.Selector], expr.Filters);
|
||||
|
||||
var ctx = new Context(this)
|
||||
{
|
||||
Cbf = expr.Cbf
|
||||
};
|
||||
|
||||
ctx.ProcessFilters(policy);
|
||||
ctx.ProcessSelectors(policy);
|
||||
|
||||
var ret = new List<List<FrostFsNodeInfo>>();
|
||||
|
||||
if (expr.Selector == null)
|
||||
{
|
||||
var lastFilter = expr.Filters[^1];
|
||||
|
||||
var subCollestion = new List<FrostFsNodeInfo>();
|
||||
ret.Add(subCollestion);
|
||||
|
||||
foreach (var nodeInfo in NodeInfoCollection)
|
||||
{
|
||||
if (ctx.Match(ctx.ProcessedFilters[lastFilter.Name], nodeInfo))
|
||||
{
|
||||
subCollestion.Add(nodeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expr.Selector.Name != null)
|
||||
{
|
||||
var sel = ctx.GetSelection(ctx.ProcessedSelectors[expr.Selector.Name]);
|
||||
|
||||
foreach (var ns in sel)
|
||||
{
|
||||
var subCollestion = new List<FrostFsNodeInfo>();
|
||||
ret.Add(subCollestion);
|
||||
foreach (var n in ns)
|
||||
{
|
||||
subCollestion.Add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal static Func<FrostFsNodeInfo, double> NewWeightFunc(INormalizer capNorm, INormalizer priceNorm)
|
||||
{
|
||||
return new Func<FrostFsNodeInfo, double>((FrostFsNodeInfo nodeInfo) =>
|
||||
{
|
||||
return capNorm.Normalize(nodeInfo.GetCapacity()) * priceNorm.Normalize(nodeInfo.Price);
|
||||
});
|
||||
}
|
||||
|
||||
private static FrostFsNodeInfo[] FlattenNodes(List<List<FrostFsNodeInfo>> nodes)
|
||||
{
|
||||
int sz = 0;
|
||||
foreach (var ns in nodes)
|
||||
{
|
||||
sz += ns.Count;
|
||||
}
|
||||
|
||||
var result = new FrostFsNodeInfo[sz];
|
||||
|
||||
int i = 0;
|
||||
foreach (var ns in nodes)
|
||||
{
|
||||
foreach (var n in ns)
|
||||
{
|
||||
result[i++] = n;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ContainerNodes returns two-dimensional list of nodes as a result of applying
|
||||
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a
|
||||
// replica descriptor. Line order corresponds to order of ReplicaDescriptor list
|
||||
// in the policy. Nodes are pre-filtered according to the Filter list from
|
||||
// the policy, and then selected by Selector list. Result is deterministic for
|
||||
// the fixed NetMap and parameters.
|
||||
//
|
||||
// Result can be used in PlacementVectors.
|
||||
public FrostFsNodeInfo[][] ContainerNodes(FrostFsPlacementPolicy p, byte[]? pivot)
|
||||
{
|
||||
var c = new Context(this)
|
||||
{
|
||||
Cbf = p.BackupFactor == 0 ? 3 : p.BackupFactor
|
||||
};
|
||||
|
||||
if (pivot != null && pivot.Length > 0)
|
||||
{
|
||||
c.HrwSeed = pivot;
|
||||
|
||||
using var murmur = new Murmur3(0);
|
||||
c.HrwSeedHash = murmur.GetCheckSum64(pivot);
|
||||
}
|
||||
|
||||
c.ProcessFilters(p);
|
||||
c.ProcessSelectors(p);
|
||||
|
||||
var unique = p.IsUnique();
|
||||
|
||||
var result = new List<List<FrostFsNodeInfo>>(p.Replicas.Length);
|
||||
for (int i = 0; i < p.Replicas.Length; i++)
|
||||
{
|
||||
result.Add([]);
|
||||
}
|
||||
|
||||
// Note that the cached selectors are not used when the policy contains the UNIQUE flag.
|
||||
// This is necessary because each selection vector affects potentially the subsequent vectors
|
||||
// and thus we call getSelection in such case, in order to take into account nodes previously
|
||||
// marked as used by earlier replicas.
|
||||
for (int i = 0; i < p.Replicas.Length; i++)
|
||||
{
|
||||
var sName = p.Replicas[i].Selector;
|
||||
|
||||
if (string.IsNullOrEmpty(sName) && !(p.Replicas.Length == 1 && p.Selectors.Count == 1))
|
||||
{
|
||||
var s = new FrostFsSelector(string.Empty)
|
||||
{
|
||||
Count = p.Replicas[i].CountNodes(),
|
||||
Filter = Context.mainFilterName
|
||||
};
|
||||
|
||||
var nodes = c.GetSelection(s);
|
||||
result[i].AddRange(FlattenNodes(nodes));
|
||||
|
||||
if (unique)
|
||||
{
|
||||
foreach (var n in result[i])
|
||||
{
|
||||
c.UsedNodes[n.Hash()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unique)
|
||||
{
|
||||
if (!c.ProcessedSelectors.TryGetValue(sName, out var s) || s == null)
|
||||
{
|
||||
throw new FrostFsException($"selector not found: {sName}");
|
||||
}
|
||||
|
||||
var nodes = c.GetSelection(c.ProcessedSelectors[sName]);
|
||||
|
||||
result[i].AddRange(FlattenNodes(nodes));
|
||||
|
||||
foreach (var n in result[i])
|
||||
{
|
||||
c.UsedNodes[n.Hash()] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var nodes = c.Selections[sName];
|
||||
result[i].AddRange(FlattenNodes(nodes));
|
||||
}
|
||||
}
|
||||
|
||||
var collection = new FrostFsNodeInfo[result.Count][];
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
{
|
||||
collection[i] = [.. result[i]];
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
80
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
80
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
using FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsNodeInfo(
|
||||
FrostFsVersion version,
|
||||
NodeState state,
|
||||
IReadOnlyCollection<string> addresses,
|
||||
IReadOnlyDictionary<string, string> attributes,
|
||||
ReadOnlyMemory<byte> publicKey) : IHasher
|
||||
{
|
||||
private ulong _hash;
|
||||
|
||||
// attrPrice is a key to the node attribute that indicates the
|
||||
// price in GAS tokens for storing one GB of data during one Epoch.
|
||||
internal const string AttrPrice = "Price";
|
||||
|
||||
// attrCapacity is a key to the node attribute that indicates the
|
||||
// total available disk space in Gigabytes.
|
||||
internal const string AttrCapacity = "Capacity";
|
||||
|
||||
// attrExternalAddr is a key for the attribute storing node external addresses.
|
||||
internal const string AttrExternalAddr = "ExternalAddr";
|
||||
|
||||
// sepExternalAddr is a separator for multi-value ExternalAddr attribute.
|
||||
internal const string SepExternalAddr = ",";
|
||||
|
||||
private ulong price = ulong.MaxValue;
|
||||
|
||||
public NodeState State { get; } = state;
|
||||
|
||||
public FrostFsVersion Version { get; } = version;
|
||||
|
||||
public IReadOnlyCollection<string> Addresses { get; } = addresses;
|
||||
|
||||
public IReadOnlyDictionary<string, string> Attributes { get; } = attributes;
|
||||
|
||||
public ReadOnlyMemory<byte> PublicKey { get; } = publicKey;
|
||||
|
||||
public ulong Hash()
|
||||
{
|
||||
if (_hash == 0)
|
||||
{
|
||||
using var murmur3 = new Murmur3(0);
|
||||
murmur3.Initialize();
|
||||
_hash = murmur3.GetCheckSum64(PublicKey.ToArray());
|
||||
}
|
||||
|
||||
return _hash;
|
||||
}
|
||||
|
||||
internal ulong GetCapacity()
|
||||
{
|
||||
if (!Attributes.TryGetValue(AttrCapacity, out var val))
|
||||
return 0;
|
||||
|
||||
return ulong.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal ulong Price
|
||||
{
|
||||
get
|
||||
{
|
||||
if (price == ulong.MaxValue)
|
||||
{
|
||||
if (!Attributes.TryGetValue(AttrPrice, out var val))
|
||||
price = 0;
|
||||
else
|
||||
price = uint.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,37 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.Client;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas)
|
||||
public struct FrostFsPlacementPolicy(bool unique,
|
||||
uint backupFactor,
|
||||
Collection<FrostFsSelector> selectors,
|
||||
Collection<FrostFsFilter> filters,
|
||||
params FrostFsReplica[] replicas)
|
||||
: IEquatable<FrostFsPlacementPolicy>
|
||||
{
|
||||
private PlacementPolicy policy;
|
||||
|
||||
public FrostFsReplica[] Replicas { get; private set; } = replicas;
|
||||
public bool Unique { get; private set; } = unique;
|
||||
public FrostFsReplica[] Replicas { get; } = replicas;
|
||||
|
||||
public Collection<FrostFsSelector> Selectors { get; } = selectors;
|
||||
|
||||
public Collection<FrostFsFilter> Filters { get; } = filters;
|
||||
|
||||
public bool Unique { get; } = unique;
|
||||
|
||||
public uint BackupFactor { get; } = backupFactor;
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var other = (FrostFsPlacementPolicy)obj;
|
||||
|
||||
|
@ -46,14 +59,10 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replic
|
|||
return policy;
|
||||
}
|
||||
|
||||
//public static FrostFsPlacementPolicy ToModel(placementPolicy)
|
||||
//{
|
||||
// return new FrostFsPlacementPolicy(
|
||||
// placementPolicy.Unique,
|
||||
// placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||
// );
|
||||
//}
|
||||
|
||||
internal readonly bool IsUnique()
|
||||
{
|
||||
return Unique || Replicas.Any(r => r.EcDataCount != 0 || r.EcParityCount != 0);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
|
@ -86,4 +95,4 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replic
|
|||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ public struct FrostFsReplica : IEquatable<FrostFsReplica>
|
|||
{
|
||||
public int Count { get; set; }
|
||||
public string Selector { get; set; }
|
||||
public uint EcDataCount { get; set; }
|
||||
public uint EcParityCount { get; set; }
|
||||
|
||||
public FrostFsReplica(int count, string? selector = null)
|
||||
{
|
||||
|
@ -18,16 +20,23 @@ public struct FrostFsReplica : IEquatable<FrostFsReplica>
|
|||
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 readonly uint CountNodes()
|
||||
{
|
||||
return Count != 0 ? (uint)Count : EcDataCount + EcParityCount;
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Count + Selector.GetHashCode();
|
||||
return (Count + Selector.GetHashCode()) ^ (int)EcDataCount ^ (int)EcParityCount;
|
||||
}
|
||||
|
||||
public static bool operator ==(FrostFsReplica left, FrostFsReplica right)
|
||||
|
@ -42,6 +51,9 @@ public struct FrostFsReplica : IEquatable<FrostFsReplica>
|
|||
|
||||
public readonly bool Equals(FrostFsReplica other)
|
||||
{
|
||||
return Count == other.Count && Selector == other.Selector;
|
||||
return Count == other.Count
|
||||
&& Selector == other.Selector
|
||||
&& EcDataCount == other.EcDataCount
|
||||
&& EcParityCount == other.EcParityCount;
|
||||
}
|
||||
}
|
||||
}
|
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsSelector(string name)
|
||||
{
|
||||
public string Name { get; set; } = name;
|
||||
public uint Count { get; set; }
|
||||
public int Clause { get; set; }
|
||||
public string? Attribute { get; set; }
|
||||
public string? Filter { get; set; }
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class FrostFsVersion(int major, int minor)
|
|||
public int Major { get; set; } = major;
|
||||
public int Minor { get; set; } = minor;
|
||||
|
||||
internal Version Version
|
||||
internal Version VersionID
|
||||
{
|
||||
get
|
||||
{
|
11
src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs
Normal file
11
src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace FrostFS.SDK
|
||||
{
|
||||
public interface IFrostFsFilter
|
||||
{
|
||||
FrostFsFilter[] Filters { get; }
|
||||
string Key { get; }
|
||||
string Name { get; }
|
||||
int Operation { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
}
|
7
src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs
Normal file
7
src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
struct NodeAttrPair
|
||||
{
|
||||
internal string attr;
|
||||
internal FrostFsNodeInfo[] nodes;
|
||||
}
|
8
src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
public enum FrostFsClause
|
||||
{
|
||||
Unspecified = 0,
|
||||
Same,
|
||||
Distinct
|
||||
}
|
456
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
456
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
|
@ -0,0 +1,456 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct Context
|
||||
{
|
||||
private const string errInvalidFilterName = "filter name is invalid";
|
||||
private const string errInvalidFilterOp = "invalid filter operation";
|
||||
private const string errFilterNotFound = "filter not found";
|
||||
private const string errNonEmptyFilters = "simple filter contains sub-filters";
|
||||
private const string errNotEnoughNodes = "not enough nodes to SELECT from";
|
||||
private const string errUnnamedTopFilter = "unnamed top-level filter";
|
||||
|
||||
internal const string mainFilterName = "*";
|
||||
internal const string likeWildcard = "*";
|
||||
|
||||
// network map to operate on
|
||||
internal FrostFsNetmapSnapshot NetMap { get; }
|
||||
|
||||
// cache of processed filters
|
||||
internal Dictionary<string, FrostFsFilter> ProcessedFilters { get; } = [];
|
||||
|
||||
// cache of processed selectors
|
||||
internal Dictionary<string, FrostFsSelector> ProcessedSelectors { get; } = [];
|
||||
|
||||
// stores results of selector processing
|
||||
internal Dictionary<string, List<List<FrostFsNodeInfo>>> Selections { get; } = [];
|
||||
|
||||
// cache of parsed numeric values
|
||||
internal Dictionary<string, ulong> NumCache { get; } = [];
|
||||
|
||||
internal byte[]? HrwSeed { get; set; }
|
||||
|
||||
// hrw.Hash of hrwSeed
|
||||
internal ulong HrwSeedHash { get; set; }
|
||||
|
||||
// container backup factor
|
||||
internal uint Cbf { get; set; }
|
||||
|
||||
// nodes already used in previous selections, which is needed when the placement
|
||||
// policy uses the UNIQUE flag. Nodes marked as used are not used in subsequent
|
||||
// base selections.
|
||||
internal Dictionary<ulong, bool> UsedNodes { get; } = [];
|
||||
|
||||
// If true, returns an error when netmap does not contain enough nodes for selection.
|
||||
// By default best effort is taken.
|
||||
internal bool Strict { get; set; }
|
||||
|
||||
// weightFunc is a weighting function for determining node priority
|
||||
// which combines low price and high performance
|
||||
private readonly Func<FrostFsNodeInfo, double> weightFunc;
|
||||
|
||||
public Context(FrostFsNetmapSnapshot netMap)
|
||||
{
|
||||
NetMap = netMap;
|
||||
weightFunc = Tools.DefaultWeightFunc(NetMap.NodeInfoCollection);
|
||||
}
|
||||
|
||||
internal void ProcessFilters(FrostFsPlacementPolicy policy)
|
||||
{
|
||||
foreach (var filter in policy.Filters)
|
||||
{
|
||||
ProcessFilter(filter, true);
|
||||
}
|
||||
}
|
||||
|
||||
readonly void ProcessFilter(FrostFsFilter filter, bool top)
|
||||
{
|
||||
var filterName = filter.Name;
|
||||
if (filterName == mainFilterName)
|
||||
{
|
||||
throw new FrostFsException($"{errInvalidFilterName}: '{errInvalidFilterName}' is reserved");
|
||||
}
|
||||
|
||||
if (top && string.IsNullOrEmpty(filterName))
|
||||
{
|
||||
throw new FrostFsException(errUnnamedTopFilter);
|
||||
}
|
||||
|
||||
if (!top && !string.IsNullOrEmpty(filterName) && !ProcessedFilters.ContainsKey(filterName))
|
||||
{
|
||||
throw new FrostFsException(errFilterNotFound);
|
||||
}
|
||||
|
||||
if (filter.Operation == (int)Operation.AND ||
|
||||
filter.Operation == (int)Operation.OR ||
|
||||
filter.Operation == (int)Operation.NOT)
|
||||
{
|
||||
foreach (var f in filter.Filters)
|
||||
ProcessFilter(f, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filter.Filters.Length != 0)
|
||||
{
|
||||
throw new FrostFsException(errNonEmptyFilters);
|
||||
}
|
||||
else if (!top && !string.IsNullOrEmpty(filterName))
|
||||
{
|
||||
// named reference
|
||||
return;
|
||||
}
|
||||
|
||||
switch (filter.Operation)
|
||||
{
|
||||
case (int)Operation.EQ:
|
||||
case (int)Operation.NE:
|
||||
case (int)Operation.LIKE:
|
||||
break;
|
||||
case (int)Operation.GT:
|
||||
case (int)Operation.GE:
|
||||
case (int)Operation.LT:
|
||||
case (int)Operation.LE:
|
||||
{
|
||||
var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture);
|
||||
NumCache[filter.Value] = n;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new FrostFsException($"{errInvalidFilterOp}: {filter.Operation}");
|
||||
}
|
||||
}
|
||||
|
||||
if (top)
|
||||
{
|
||||
ProcessedFilters[filterName] = filter;
|
||||
}
|
||||
}
|
||||
|
||||
// processSelectors processes selectors and returns error is any of them is invalid.
|
||||
internal void ProcessSelectors(FrostFsPlacementPolicy policy)
|
||||
{
|
||||
foreach (var selector in policy.Selectors)
|
||||
{
|
||||
var filterName = selector.Filter;
|
||||
if (filterName != mainFilterName)
|
||||
{
|
||||
if (selector.Filter == null || !ProcessedFilters.ContainsKey(selector.Filter))
|
||||
{
|
||||
throw new FrostFsException($"{errFilterNotFound}: SELECT FROM '{filterName}'");
|
||||
}
|
||||
}
|
||||
|
||||
ProcessedSelectors[selector.Name] = selector;
|
||||
|
||||
var selection = GetSelection(selector);
|
||||
|
||||
Selections[selector.Name] = selection;
|
||||
}
|
||||
}
|
||||
|
||||
// calcNodesCount returns number of buckets and minimum number of nodes in every bucket
|
||||
// for the given selector.
|
||||
static (int bucketCount, int nodesInBucket) CalcNodesCount(FrostFsSelector selector)
|
||||
{
|
||||
return selector.Clause == (int)FrostFsClause.Same
|
||||
? (1, (int)selector.Count)
|
||||
: ((int)selector.Count, 1);
|
||||
}
|
||||
|
||||
// getSelectionBase returns nodes grouped by selector attribute.
|
||||
// It it guaranteed that each pair will contain at least one node.
|
||||
internal NodeAttrPair[] GetSelectionBase(FrostFsSelector selector)
|
||||
{
|
||||
var fName = selector.Filter ?? throw new FrostFsException("Filter name for selector is empty");
|
||||
|
||||
_ = ProcessedFilters.TryGetValue(fName, out var f);
|
||||
|
||||
var isMain = fName == mainFilterName;
|
||||
var result = new List<NodeAttrPair>();
|
||||
|
||||
var nodeMap = new Dictionary<string, List<FrostFsNodeInfo>>();
|
||||
var attr = selector.Attribute;
|
||||
|
||||
foreach (var node in NetMap.NodeInfoCollection)
|
||||
{
|
||||
if (UsedNodes.ContainsKey(node.Hash()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isMain || Match(f, node))
|
||||
{
|
||||
if (attr == null)
|
||||
{
|
||||
// Default attribute is transparent identifier which is different for every node.
|
||||
result.Add(new NodeAttrPair { attr = "", nodes = [node] });
|
||||
}
|
||||
else
|
||||
{
|
||||
var v = node.Attributes[attr];
|
||||
if (!nodeMap.TryGetValue(v, out var nodes) || nodes == null)
|
||||
{
|
||||
nodeMap[v] = [];
|
||||
}
|
||||
|
||||
nodeMap[v].Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(attr))
|
||||
{
|
||||
foreach (var v in nodeMap)
|
||||
{
|
||||
result.Add(new NodeAttrPair() { attr = v.Key, nodes = [.. v.Value] });
|
||||
}
|
||||
}
|
||||
|
||||
if (HrwSeed != null && HrwSeed.Length != 0)
|
||||
{
|
||||
double[] ws = [];
|
||||
|
||||
var sortedNodes = new NodeAttrPair[result.Count];
|
||||
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
{
|
||||
var res = result[i];
|
||||
Tools.AppendWeightsTo(res.nodes, weightFunc, ref ws);
|
||||
sortedNodes[i].nodes = Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash).ToArray();
|
||||
sortedNodes[i].attr = result[i].attr;
|
||||
}
|
||||
|
||||
return sortedNodes;
|
||||
}
|
||||
return [.. result];
|
||||
}
|
||||
|
||||
static double CalcBucketWeight(List<FrostFsNodeInfo> ns, IAggregator a, Func<FrostFsNodeInfo, double> wf)
|
||||
{
|
||||
foreach (var node in ns)
|
||||
{
|
||||
a.Add(wf(node));
|
||||
}
|
||||
|
||||
return a.Compute();
|
||||
}
|
||||
|
||||
// getSelection returns nodes grouped by s.attribute.
|
||||
// Last argument specifies if more buckets can be used to fulfill CBF.
|
||||
internal List<List<FrostFsNodeInfo>> GetSelection(FrostFsSelector s)
|
||||
{
|
||||
var (bucketCount, nodesInBucket) = CalcNodesCount(s);
|
||||
|
||||
var buckets = GetSelectionBase(s);
|
||||
|
||||
if (Strict && buckets.Length < bucketCount)
|
||||
throw new FrostFsException($"errNotEnoughNodes: '{s.Name}'");
|
||||
|
||||
// We need deterministic output in case there is no pivot.
|
||||
// If pivot is set, buckets are sorted by HRW.
|
||||
// However, because initial order influences HRW order for buckets with equal weights,
|
||||
// we also need to have deterministic input to HRW sorting routine.
|
||||
if (HrwSeed == null || HrwSeed.Length == 0)
|
||||
{
|
||||
buckets = string.IsNullOrEmpty(s.Attribute)
|
||||
? [.. buckets.OrderBy(b => b.nodes[0].Hash())]
|
||||
: [.. buckets.OrderBy(b => b.attr)];
|
||||
}
|
||||
|
||||
var maxNodesInBucket = nodesInBucket * (int)Cbf;
|
||||
|
||||
var res = new List<List<FrostFsNodeInfo>>(buckets.Length);
|
||||
var fallback = new List<List<FrostFsNodeInfo>>(buckets.Length);
|
||||
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
var ns = buckets[i].nodes;
|
||||
if (ns.Length >= maxNodesInBucket)
|
||||
{
|
||||
res.Add(new List<FrostFsNodeInfo>(ns[..maxNodesInBucket]));
|
||||
}
|
||||
else if (ns.Length >= nodesInBucket)
|
||||
{
|
||||
fallback.Add(new List<FrostFsNodeInfo>(ns));
|
||||
}
|
||||
}
|
||||
|
||||
if (res.Count < bucketCount)
|
||||
{
|
||||
// Fallback to using minimum allowed backup factor (1).
|
||||
res.AddRange(fallback);
|
||||
|
||||
if (Strict && res.Count < bucketCount)
|
||||
{
|
||||
throw new FrostFsException($"{errNotEnoughNodes}: {s}");
|
||||
}
|
||||
}
|
||||
|
||||
if (HrwSeed != null && HrwSeed.Length != 0)
|
||||
{
|
||||
var weights = new double[res.Count];
|
||||
var a = new MeanIQRAgg();
|
||||
|
||||
for (int i = 0; i < res.Count; i++)
|
||||
{
|
||||
a.Clear();
|
||||
weights[i] = CalcBucketWeight(res[i], a, weightFunc);
|
||||
}
|
||||
|
||||
var hashers = res.Select(r => new HasherList(r)).ToList();
|
||||
hashers = Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash);
|
||||
|
||||
for (int i = 0; i < res.Count; i++)
|
||||
{
|
||||
res[i] = hashers[i].Nodes;
|
||||
}
|
||||
}
|
||||
|
||||
if (res.Count < bucketCount)
|
||||
{
|
||||
if (Strict && res.Count == 0)
|
||||
{
|
||||
throw new FrostFsException(errNotEnoughNodes);
|
||||
}
|
||||
|
||||
bucketCount = res.Count;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(s.Attribute))
|
||||
{
|
||||
fallback = res.Skip(bucketCount).ToList();
|
||||
res = res.Take(bucketCount).ToList();
|
||||
|
||||
for (int i = 0; i < fallback.Count; i++)
|
||||
{
|
||||
var index = i % bucketCount;
|
||||
if (res[index].Count >= maxNodesInBucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
res[index].AddRange(fallback[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return res.Take(bucketCount).ToList();
|
||||
}
|
||||
|
||||
internal bool MatchKeyValue(FrostFsFilter f, FrostFsNodeInfo nodeInfo)
|
||||
{
|
||||
switch (f.Operation)
|
||||
{
|
||||
case (int)Operation.EQ:
|
||||
return nodeInfo.Attributes.TryGetValue(f.Key, out var val) && val == f.Value;
|
||||
case (int)Operation.LIKE:
|
||||
{
|
||||
var hasPrefix = f.Value.StartsWith(likeWildcard, StringComparison.Ordinal);
|
||||
var hasSuffix = f.Value.EndsWith(likeWildcard, StringComparison.Ordinal);
|
||||
|
||||
var start = hasPrefix ? likeWildcard.Length : 0;
|
||||
var end = hasSuffix ? f.Value.Length - likeWildcard.Length : f.Value.Length;
|
||||
var str = f.Value[start..end];
|
||||
|
||||
if (hasPrefix && hasSuffix)
|
||||
return nodeInfo.Attributes[f.Key].Contains(str);
|
||||
|
||||
if (hasPrefix && !hasSuffix)
|
||||
return nodeInfo.Attributes[f.Key].EndsWith(str, StringComparison.Ordinal);
|
||||
|
||||
if (!hasPrefix && hasSuffix)
|
||||
return nodeInfo.Attributes[f.Key].StartsWith(str, StringComparison.Ordinal);
|
||||
|
||||
|
||||
return nodeInfo.Attributes[f.Key] == f.Value;
|
||||
}
|
||||
case (int)Operation.NE:
|
||||
return nodeInfo.Attributes[f.Key] != f.Value;
|
||||
default:
|
||||
{
|
||||
ulong attr;
|
||||
switch (f.Key)
|
||||
{
|
||||
case FrostFsNodeInfo.AttrPrice:
|
||||
attr = nodeInfo.Price;
|
||||
break;
|
||||
|
||||
case FrostFsNodeInfo.AttrCapacity:
|
||||
attr = nodeInfo.GetCapacity();
|
||||
break;
|
||||
default:
|
||||
if (!ulong.TryParse(nodeInfo.Attributes[f.Key], NumberStyles.Integer, CultureInfo.InvariantCulture, out attr))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (f.Operation)
|
||||
{
|
||||
case (int)Operation.GT:
|
||||
return attr > NumCache[f.Value];
|
||||
case (int)Operation.GE:
|
||||
return attr >= NumCache[f.Value];
|
||||
case (int)Operation.LT:
|
||||
return attr < NumCache[f.Value];
|
||||
case (int)Operation.LE:
|
||||
return attr <= NumCache[f.Value];
|
||||
default:
|
||||
// do nothing and return false
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// will not happen if context was created from f (maybe panic?)
|
||||
return false;
|
||||
}
|
||||
|
||||
// match matches f against b. It returns no errors because
|
||||
// filter should have been parsed during context creation
|
||||
// and missing node properties are considered as a regular fail.
|
||||
internal bool Match(FrostFsFilter f, FrostFsNodeInfo nodeInfo)
|
||||
{
|
||||
switch (f.Operation)
|
||||
{
|
||||
case (int)Operation.NOT:
|
||||
{
|
||||
var inner = f.Filters;
|
||||
var fSub = inner[0];
|
||||
|
||||
if (!string.IsNullOrEmpty(inner[0].Name))
|
||||
{
|
||||
fSub = ProcessedFilters[inner[0].Name];
|
||||
}
|
||||
return !Match(fSub, nodeInfo);
|
||||
}
|
||||
case (int)Operation.AND:
|
||||
case (int)Operation.OR:
|
||||
{
|
||||
for (int i = 0; i < f.Filters.Length; i++)
|
||||
{
|
||||
var fSub = f.Filters[i];
|
||||
|
||||
if (!string.IsNullOrEmpty(f.Filters[i].Name))
|
||||
{
|
||||
fSub = ProcessedFilters[f.Filters[i].Name];
|
||||
}
|
||||
|
||||
var ok = Match(fSub, nodeInfo);
|
||||
|
||||
if (ok == (f.Operation == (int)Operation.OR))
|
||||
{
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
return f.Operation == (int)Operation.AND;
|
||||
}
|
||||
default:
|
||||
return MatchKeyValue(f, nodeInfo);
|
||||
}
|
||||
}
|
||||
}
|
26
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
26
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal sealed class HasherList : IHasher
|
||||
{
|
||||
private readonly List<FrostFsNodeInfo> _nodes;
|
||||
|
||||
internal HasherList(List<FrostFsNodeInfo> nodes)
|
||||
{
|
||||
_nodes = nodes;
|
||||
}
|
||||
|
||||
internal List<FrostFsNodeInfo> Nodes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _nodes;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong Hash()
|
||||
{
|
||||
return _nodes.Count > 0 ? _nodes[0].Hash() : 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal interface IAggregator
|
||||
{
|
||||
void Add(double d);
|
||||
|
||||
double Compute();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal interface IHasher
|
||||
{
|
||||
ulong Hash();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
interface INormalizer
|
||||
{
|
||||
double Normalize(double w);
|
||||
}
|
20
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs
Normal file
20
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct MeanAgg
|
||||
{
|
||||
private double mean;
|
||||
private int count;
|
||||
|
||||
internal void Add(double n)
|
||||
{
|
||||
int c = count + 1;
|
||||
mean = mean * count / c + n / c;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
internal readonly double Compute()
|
||||
{
|
||||
return mean;
|
||||
}
|
||||
}
|
65
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs
Normal file
65
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct MeanIQRAgg : IAggregator
|
||||
{
|
||||
private const int minLn = 4;
|
||||
internal Collection<double> arr = [];
|
||||
|
||||
public MeanIQRAgg()
|
||||
{
|
||||
}
|
||||
|
||||
public readonly void Add(double d)
|
||||
{
|
||||
arr.Add(d);
|
||||
}
|
||||
|
||||
public readonly double Compute()
|
||||
{
|
||||
var length = arr.Count;
|
||||
if (length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var sorted = arr.OrderBy(p => p).ToArray();
|
||||
|
||||
double minV, maxV;
|
||||
|
||||
if (arr.Count < minLn)
|
||||
{
|
||||
minV = sorted[0];
|
||||
maxV = sorted[length - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
var start = length / minLn;
|
||||
var end = length * 3 / minLn - 1;
|
||||
|
||||
minV = sorted[start];
|
||||
maxV = sorted[end];
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
double sum = 0;
|
||||
|
||||
foreach (var e in sorted)
|
||||
{
|
||||
if (e >= minV && e <= maxV)
|
||||
{
|
||||
sum += e;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
internal readonly void Clear()
|
||||
{
|
||||
arr.Clear();
|
||||
}
|
||||
}
|
27
src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs
Normal file
27
src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct MinAgg
|
||||
{
|
||||
private double min;
|
||||
private bool minFound;
|
||||
|
||||
internal void Add(double n)
|
||||
{
|
||||
if (!minFound)
|
||||
{
|
||||
min = n;
|
||||
minFound = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n < min)
|
||||
{
|
||||
min = n;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly double Compute()
|
||||
{
|
||||
return min;
|
||||
}
|
||||
}
|
16
src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs
Normal file
16
src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
public enum Operation
|
||||
{
|
||||
Unspecified = 0,
|
||||
EQ,
|
||||
NE,
|
||||
GT,
|
||||
GE,
|
||||
LT,
|
||||
LE,
|
||||
OR,
|
||||
AND,
|
||||
NOT,
|
||||
LIKE
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct ReverseMinNorm : INormalizer
|
||||
{
|
||||
internal double min;
|
||||
|
||||
public readonly double Normalize(double w) => (min + 1) / (w + 1);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal struct SelectFilterExpr(uint cbf, FrostFsSelector selector, Collection<FrostFsFilter> filters)
|
||||
{
|
||||
internal uint Cbf { get; } = cbf;
|
||||
internal FrostFsSelector Selector { get; } = selector;
|
||||
internal Collection<FrostFsFilter> Filters { get; } = filters;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
internal readonly struct SigmoidNorm : INormalizer
|
||||
{
|
||||
private readonly double _scale;
|
||||
|
||||
internal SigmoidNorm(double scale)
|
||||
{
|
||||
_scale = scale;
|
||||
}
|
||||
|
||||
public readonly double Normalize(double w)
|
||||
{
|
||||
if (_scale == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var x = w / _scale;
|
||||
|
||||
return x / (1 + x);
|
||||
}
|
||||
}
|
138
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
138
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using static FrostFS.SDK.FrostFsNetmapSnapshot;
|
||||
|
||||
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||
|
||||
public static class Tools
|
||||
{
|
||||
internal static ulong Distance(ulong x, ulong y)
|
||||
{
|
||||
var acc = x ^ y;
|
||||
acc ^= acc >> 33;
|
||||
acc *= 0xff51afd7ed558ccd;
|
||||
acc ^= acc >> 33;
|
||||
acc *= 0xc4ceb9fe1a85ec53;
|
||||
acc ^= acc >> 33;
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
internal static double ReverceNormalize(double r, double w)
|
||||
{
|
||||
return (r + 1) / (w + 1);
|
||||
}
|
||||
|
||||
internal static double Normalize(double r, double w)
|
||||
{
|
||||
if (r == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var x = w / r;
|
||||
return x / (1 + x);
|
||||
}
|
||||
|
||||
internal static void AppendWeightsTo(FrostFsNodeInfo[] nodes, Func<FrostFsNodeInfo, double> wf, ref double[] weights)
|
||||
{
|
||||
if (weights.Length < nodes.Length)
|
||||
{
|
||||
weights = new double[nodes.Length];
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodes.Length; i++)
|
||||
{
|
||||
weights[i] = wf(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<T> SortHasherSliceByWeightValue<T>(List<T> nodes, Span<double> weights, ulong hash) where T : IHasher
|
||||
{
|
||||
if (nodes.Count == 0)
|
||||
{
|
||||
return nodes;
|
||||
}
|
||||
|
||||
var allEquals = true;
|
||||
|
||||
if (weights.Length > 1)
|
||||
{
|
||||
for (int i = 1; i < weights.Length; i++)
|
||||
{
|
||||
if (weights[i] != weights[0])
|
||||
{
|
||||
allEquals = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dist = new double[nodes.Count];
|
||||
|
||||
if (allEquals)
|
||||
{
|
||||
for (int i = 0; i < dist.Length; i++)
|
||||
{
|
||||
var x = nodes[i].Hash();
|
||||
dist[i] = Distance(x, hash);
|
||||
}
|
||||
|
||||
return SortHasherByDistance(nodes, dist, true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dist.Length; i++)
|
||||
{
|
||||
var d = Distance(nodes[i].Hash(), hash);
|
||||
dist[i] = (ulong.MaxValue - d) * weights[i];
|
||||
}
|
||||
|
||||
return SortHasherByDistance(nodes, dist, false);
|
||||
}
|
||||
|
||||
internal static List<T> SortHasherByDistance<T, N>(List<T> nodes, N[] dist, bool asc)
|
||||
{
|
||||
IndexedValue<T, N>[] indexes = new IndexedValue<T, N>[nodes.Count];
|
||||
for (int i = 0; i < dist.Length; i++)
|
||||
{
|
||||
indexes[i] = new IndexedValue<T, N>() { nodeInfo = nodes[i], dist = dist[i] };
|
||||
}
|
||||
|
||||
if (asc)
|
||||
{
|
||||
return new List<T>(indexes
|
||||
.OrderBy(x => x.dist)
|
||||
.Select(x => x.nodeInfo).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<T>(indexes
|
||||
.OrderByDescending(x => x.dist)
|
||||
.Select(x => x.nodeInfo));
|
||||
}
|
||||
}
|
||||
|
||||
internal static Func<FrostFsNodeInfo, double> DefaultWeightFunc(IReadOnlyList<FrostFsNodeInfo> nodes)
|
||||
{
|
||||
MeanAgg mean = new();
|
||||
MinAgg minV = new();
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
mean.Add(node.GetCapacity());
|
||||
minV.Add(node.Price);
|
||||
}
|
||||
|
||||
return NewWeightFunc(
|
||||
NewSigmoidNorm(mean.Compute()),
|
||||
NewReverseMinNorm(minV.Compute()));
|
||||
}
|
||||
|
||||
private struct IndexedValue<T, N>
|
||||
{
|
||||
internal T nodeInfo;
|
||||
internal N dist;
|
||||
}
|
||||
}
|
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 FrostFsHeaderResult
|
||||
{
|
||||
public FrostFsObjectHeader? HeaderInfo { get; internal set; }
|
||||
|
||||
public FrostFsSplitInfo? SplitInfo { get; internal set; }
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
|
||||
using FrostFS.Object;
|
||||
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
|
@ -8,13 +8,8 @@ public class FrostFsObjectId(string id)
|
|||
{
|
||||
public string Value { get; } = id;
|
||||
|
||||
public static FrostFsObjectId FromHash(byte[] hash)
|
||||
public static FrostFsObjectId FromHash(ReadOnlySpan<byte> hash)
|
||||
{
|
||||
if (hash is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hash));
|
||||
}
|
||||
|
||||
if (hash.Length != Constants.Sha256HashLength)
|
||||
throw new FormatException("ObjectID must be a sha256 hash.");
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsOwner(string id)
|
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;
|
||||
}
|
||||
}
|
|
@ -24,5 +24,4 @@ public class FrostFsSplit(SplitId splitId,
|
|||
public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader;
|
||||
|
||||
public ReadOnlyCollection<FrostFsObjectId>? Children { get; } = children;
|
||||
|
||||
}
|
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs
Normal file
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using FrostFS.Object;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsSplitInfo
|
||||
{
|
||||
private readonly SplitInfo _splitInfo;
|
||||
|
||||
private SplitId? _splitId;
|
||||
|
||||
private FrostFsObjectId? _link;
|
||||
|
||||
private FrostFsObjectId? _lastPart;
|
||||
|
||||
internal FrostFsSplitInfo(SplitInfo splitInfo)
|
||||
{
|
||||
_splitInfo = splitInfo;
|
||||
}
|
||||
|
||||
public SplitId SplitId => _splitId ??= new SplitId(_splitInfo.SplitId.ToUuid());
|
||||
|
||||
public FrostFsObjectId? Link => _link ??= _splitInfo.Link == null
|
||||
? null : FrostFsObjectId.FromHash(_splitInfo.Link.Value.Span);
|
||||
|
||||
public FrostFsObjectId? LastPart => _lastPart ??= _splitInfo.LastPart == null
|
||||
? null : FrostFsObjectId.FromHash(_splitInfo.LastPart.Value.Span);
|
||||
}
|
|
@ -6,5 +6,5 @@ namespace FrostFS.SDK;
|
|||
|
||||
public interface IObjectReader : IDisposable
|
||||
{
|
||||
Task<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
|
||||
ValueTask<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
|
||||
}
|
117
src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
Normal file
117
src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
|
||||
using FrostFS.Refs;
|
||||
|
||||
using FrostFS.SDK.Client;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using FrostFS.Session;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK;
|
||||
|
||||
public class FrostFsSessionToken
|
||||
{
|
||||
private Guid _id;
|
||||
private ReadOnlyMemory<byte> _sessionKey;
|
||||
private readonly SessionToken.Types.Body _body;
|
||||
|
||||
private FrostFsSessionToken()
|
||||
{
|
||||
ProtoId = ByteString.Empty;
|
||||
ProtoSessionKey = ByteString.Empty;
|
||||
_body = new SessionToken.Types.Body();
|
||||
}
|
||||
|
||||
internal FrostFsSessionToken(SessionToken token)
|
||||
{
|
||||
ProtoId = token.Body.Id;
|
||||
ProtoSessionKey = token.Body.SessionKey;
|
||||
|
||||
_body = token.Body;
|
||||
}
|
||||
|
||||
public Guid Id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_id == Guid.Empty)
|
||||
_id = ProtoId.ToUuid();
|
||||
|
||||
return _id;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<byte> SessionKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sessionKey.IsEmpty)
|
||||
_sessionKey = ProtoSessionKey.Memory;
|
||||
|
||||
return _sessionKey;
|
||||
}
|
||||
}
|
||||
|
||||
internal ByteString ProtoId { get; }
|
||||
|
||||
internal ByteString ProtoSessionKey { get; }
|
||||
|
||||
public SessionToken CreateContainerToken(ContainerID? containerId, ContainerSessionContext.Types.Verb verb, ClientKey key)
|
||||
{
|
||||
if (key is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
SessionToken sessionToken = new() { Body = _body.Clone() };
|
||||
|
||||
sessionToken.Body.Container = new() { Verb = verb };
|
||||
|
||||
if (containerId != null)
|
||||
sessionToken.Body.Container.ContainerId = containerId;
|
||||
else
|
||||
sessionToken.Body.Container.Wildcard = true;
|
||||
|
||||
sessionToken.Body.SessionKey = key.PublicKeyProto;
|
||||
|
||||
sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body);
|
||||
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
public SessionToken CreateObjectTokenContext(Address address, ObjectSessionContext.Types.Verb verb, ClientKey key)
|
||||
{
|
||||
if (address is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
}
|
||||
|
||||
if (key is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
SessionToken sessionToken = new()
|
||||
{
|
||||
Body = _body.Clone()
|
||||
};
|
||||
|
||||
ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId };
|
||||
|
||||
if (address.ObjectId != null)
|
||||
target.Objects.Add(address.ObjectId);
|
||||
|
||||
sessionToken.Body.Object = new()
|
||||
{
|
||||
Target = target,
|
||||
Verb = verb
|
||||
};
|
||||
|
||||
sessionToken.Body.SessionKey = key.PublicKeyProto;
|
||||
|
||||
sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body);
|
||||
|
||||
return sessionToken;
|
||||
}
|
||||
}
|
68
src/FrostFS.SDK.Client/ObjectWriter.cs
Normal file
68
src/FrostFS.SDK.Client/ObjectWriter.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using FrostFS.Object;
|
||||
using FrostFS.SDK.Client.Interfaces;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace FrostFS.SDK.Client
|
||||
{
|
||||
internal sealed class ObjectWriter : IObjectWriter
|
||||
{
|
||||
private readonly ClientContext ctx;
|
||||
private readonly PrmObjectPutBase args;
|
||||
private readonly ObjectStreamer<PutRequest, PutResponse> streamer;
|
||||
private bool disposedValue;
|
||||
|
||||
internal ObjectWriter(ClientContext ctx, PrmObjectPutBase args, ObjectStreamer<PutRequest, PutResponse> streamer)
|
||||
{
|
||||
this.ctx = ctx;
|
||||
this.args = args;
|
||||
this.streamer = streamer;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
var chunkRequest = new PutRequest
|
||||
{
|
||||
Body = new PutRequest.Types.Body
|
||||
{
|
||||
Chunk = UnsafeByteOperations.UnsafeWrap(memory)
|
||||
}
|
||||
};
|
||||
|
||||
chunkRequest.Sign(this.ctx.Key.ECDsaKey);
|
||||
|
||||
await streamer.Write(chunkRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<FrostFsObjectId> CompleteAsync()
|
||||
{
|
||||
var response = await streamer.Close().ConfigureAwait(false);
|
||||
|
||||
Verifier.CheckResponse(response);
|
||||
|
||||
return FrostFsObjectId.FromHash(response.Body.ObjectId.Value.Span);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
streamer.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
44
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
44
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public readonly struct CallContext(TimeSpan timeout, CancellationToken cancellationToken = default) : IEquatable<CallContext>
|
||||
{
|
||||
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||
|
||||
public TimeSpan Timeout { get; } = timeout;
|
||||
|
||||
internal readonly DateTime? GetDeadline()
|
||||
{
|
||||
return Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || obj is not CallContext)
|
||||
return false;
|
||||
|
||||
return Equals((CallContext)obj);
|
||||
}
|
||||
|
||||
public bool Equals(CallContext other)
|
||||
{
|
||||
return Timeout == other.Timeout && CancellationToken.Equals(other.CancellationToken);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return CancellationToken.GetHashCode() ^ Timeout.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(CallContext left, CallContext right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CallContext left, CallContext right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public interface ISessionToken
|
||||
{
|
||||
|
@ -8,5 +8,5 @@ public interface ISessionToken
|
|||
/// 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; }
|
||||
FrostFsSessionToken? SessionToken { get; }
|
||||
}
|
43
src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
Normal file
43
src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable<PrmApeChainAdd>
|
||||
{
|
||||
public FrostFsChainTarget Target { get; } = target;
|
||||
|
||||
public FrostFsChain Chain { get; } = chain;
|
||||
|
||||
/// <summary>
|
||||
/// FrostFS request X-Headers
|
||||
/// </summary>
|
||||
public string[] XHeaders { get; } = xheaders ?? [];
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || obj is not PrmApeChainAdd)
|
||||
return false;
|
||||
|
||||
return Equals((PrmApeChainAdd)obj);
|
||||
}
|
||||
|
||||
public readonly bool Equals(PrmApeChainAdd other)
|
||||
{
|
||||
return Target == other.Target
|
||||
&& Chain == other.Chain
|
||||
&& XHeaders == other.XHeaders;
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(PrmApeChainAdd left, PrmApeChainAdd right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PrmApeChainAdd left, PrmApeChainAdd right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
40
src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
Normal file
40
src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public readonly struct PrmApeChainList(FrostFsChainTarget target, string[]? xheaders = null) : System.IEquatable<PrmApeChainList>
|
||||
{
|
||||
public FrostFsChainTarget Target { get; } = target;
|
||||
|
||||
/// <summary>
|
||||
/// FrostFS request X-Headers
|
||||
/// </summary>
|
||||
public string[] XHeaders { get; } = xheaders ?? [];
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || obj is not PrmApeChainList)
|
||||
return false;
|
||||
|
||||
return Equals((PrmApeChainList)obj);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Target.GetHashCode() ^ XHeaders.GetHashCode();
|
||||
}
|
||||
|
||||
public readonly bool Equals(PrmApeChainList other)
|
||||
{
|
||||
return Target == other.Target
|
||||
&& XHeaders == other.XHeaders;
|
||||
}
|
||||
|
||||
public static bool operator ==(PrmApeChainList left, PrmApeChainList right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PrmApeChainList left, PrmApeChainList right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
46
src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
Normal file
46
src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
namespace FrostFS.SDK.Client;
|
||||
|
||||
public readonly struct PrmApeChainRemove(
|
||||
FrostFsChainTarget target,
|
||||
FrostFsChain chain,
|
||||
string[]? xheaders = null) : System.IEquatable<PrmApeChainRemove>
|
||||
{
|
||||
public FrostFsChainTarget Target { get; } = target;
|
||||
|
||||
public FrostFsChain Chain { get; } = chain;
|
||||
|
||||
/// <summary>
|
||||
/// FrostFS request X-Headers
|
||||
/// </summary>
|
||||
public string[] XHeaders { get; } = xheaders ?? [];
|
||||
|
||||
public override readonly bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || obj is not PrmApeChainRemove)
|
||||
return false;
|
||||
|
||||
return Equals((PrmApeChainRemove)obj);
|
||||
}
|
||||
|
||||
public readonly bool Equals(PrmApeChainRemove other)
|
||||
{
|
||||
return Target == other.Target
|
||||
&& Chain == other.Chain
|
||||
&& XHeaders == other.XHeaders;
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(PrmApeChainRemove left, PrmApeChainRemove right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PrmApeChainRemove left, PrmApeChainRemove right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue