[#24] Client: Implement pool part1
All checks were successful
DCO / DCO (pull_request) Successful in 46s
All checks were successful
DCO / DCO (pull_request) Successful in 46s
first iteration - base classes and methods Signed-off-by: Pavel Gross <p.gross@yadro.com>
This commit is contained in:
parent
d1271df207
commit
c9a75ea025
72 changed files with 2786 additions and 468 deletions
|
@ -1,23 +0,0 @@
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2
|
|
||||||
{
|
|
||||||
internal static class Cache
|
|
||||||
{
|
|
||||||
private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
|
|
||||||
{
|
|
||||||
// TODO: get from options?
|
|
||||||
SizeLimit = 256
|
|
||||||
});
|
|
||||||
|
|
||||||
private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
|
|
||||||
{
|
|
||||||
// TODO: get from options?
|
|
||||||
SizeLimit = 1024
|
|
||||||
});
|
|
||||||
|
|
||||||
internal static IMemoryCache Owners => _ownersCache;
|
|
||||||
|
|
||||||
internal static IMemoryCache Containers => _containersCache;
|
|
||||||
}
|
|
||||||
}
|
|
22
src/FrostFS.SDK.ClientV2/Caches.cs
Normal file
22
src/FrostFS.SDK.ClientV2/Caches.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal static class Caches
|
||||||
|
{
|
||||||
|
private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
|
||||||
|
{
|
||||||
|
// TODO: get from options?
|
||||||
|
SizeLimit = 256
|
||||||
|
});
|
||||||
|
|
||||||
|
private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
|
||||||
|
{
|
||||||
|
// TODO: get from options?
|
||||||
|
SizeLimit = 1024
|
||||||
|
});
|
||||||
|
|
||||||
|
internal static IMemoryCache Owners => _ownersCache;
|
||||||
|
|
||||||
|
internal static IMemoryCache Containers => _containersCache;
|
||||||
|
}
|
|
@ -4,12 +4,11 @@ using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2
|
namespace FrostFS.SDK.ClientV2;
|
||||||
{
|
|
||||||
public class ClientKey(ECDsa key)
|
|
||||||
{
|
|
||||||
internal ECDsa ECDsaKey { get; } = key;
|
|
||||||
|
|
||||||
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
|
public class ClientKey(ECDsa key)
|
||||||
}
|
{
|
||||||
|
internal ECDsa ECDsaKey { get; } = key;
|
||||||
|
|
||||||
|
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class SessionExpiredException : FrostFsException
|
||||||
|
{
|
||||||
|
public SessionExpiredException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionExpiredException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionExpiredException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class SessionNotFoundException : FrostFsException
|
||||||
|
{
|
||||||
|
public SessionNotFoundException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionNotFoundException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,12 @@ using System.Threading.Tasks;
|
||||||
using Frostfs.V2.Ape;
|
using Frostfs.V2.Ape;
|
||||||
using Frostfs.V2.Apemanager;
|
using Frostfs.V2.Apemanager;
|
||||||
|
|
||||||
|
using FrostFS.Accounting;
|
||||||
using FrostFS.Container;
|
using FrostFS.Container;
|
||||||
using FrostFS.Netmap;
|
using FrostFS.Netmap;
|
||||||
using FrostFS.Object;
|
using FrostFS.Object;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.ClientV2.Services;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
|
|
||||||
|
@ -35,7 +37,9 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
|
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
|
||||||
|
|
||||||
internal ClientEnvironment ClientCtx { get; set; }
|
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
|
||||||
|
|
||||||
|
internal EnvironmentContext ClientCtx { get; set; }
|
||||||
|
|
||||||
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +93,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
var ecdsaKey = settings.Value.Key.LoadWif();
|
var ecdsaKey = settings.Value.Key.LoadWif();
|
||||||
FrostFsOwner.FromKey(ecdsaKey);
|
FrostFsOwner.FromKey(ecdsaKey);
|
||||||
|
|
||||||
ClientCtx = new ClientEnvironment(
|
ClientCtx = new EnvironmentContext(
|
||||||
client: this,
|
client: this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
@ -110,7 +114,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||||
|
|
||||||
ClientCtx = new ClientEnvironment(
|
ClientCtx = new EnvironmentContext(
|
||||||
this,
|
this,
|
||||||
key: null,
|
key: null,
|
||||||
owner: null,
|
owner: null,
|
||||||
|
@ -131,7 +135,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
|
|
||||||
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
||||||
|
|
||||||
ClientCtx = new ClientEnvironment(
|
ClientCtx = new EnvironmentContext(
|
||||||
this,
|
this,
|
||||||
key: ecdsaKey,
|
key: ecdsaKey,
|
||||||
owner: FrostFsOwner.FromKey(ecdsaKey),
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
@ -142,6 +146,16 @@ public class FrostFSClient : IFrostFSClient
|
||||||
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal FrostFSClient(WrapperPrm prm)
|
||||||
|
{
|
||||||
|
ClientCtx = new EnvironmentContext(
|
||||||
|
client: this,
|
||||||
|
key: prm.Key,
|
||||||
|
owner: FrostFsOwner.FromKey(prm.Key!),
|
||||||
|
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
|
||||||
|
version: new FrostFsVersion(2, 13));
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
@ -305,16 +319,16 @@ public class FrostFSClient : IFrostFSClient
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SessionImplementation
|
#region Session Implementation
|
||||||
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
||||||
{
|
{
|
||||||
if (args is null)
|
if (args is null)
|
||||||
throw new ArgumentNullException(nameof(args));
|
throw new ArgumentNullException(nameof(args));
|
||||||
|
|
||||||
var session = await CreateSessionInternalAsync(args).ConfigureAwait(false);
|
var session = await CreateSessionInternalAsync(args).ConfigureAwait(false);
|
||||||
var token = session.Serialize();
|
|
||||||
|
|
||||||
return new FrostFsSessionToken(token);
|
var token = session.Serialize();
|
||||||
|
return new FrostFsSessionToken(token, session.Body.Id.ToUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args)
|
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args)
|
||||||
|
@ -327,8 +341,18 @@ public class FrostFSClient : IFrostFSClient
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Accounting Implementation
|
||||||
|
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args)
|
||||||
|
{
|
||||||
|
args ??= new PrmBalance();
|
||||||
|
|
||||||
|
var service = GetAccouningService(args);
|
||||||
|
return await service.GetBallance(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region ToolsImplementation
|
#region ToolsImplementation
|
||||||
public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx)
|
public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (header == null)
|
if (header == null)
|
||||||
throw new ArgumentNullException(nameof(header));
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
@ -337,7 +361,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private async void CheckFrostFsVersionSupport(Context? ctx = default)
|
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
|
||||||
{
|
{
|
||||||
var args = new PrmNodeInfo { Context = ctx };
|
var args = new PrmNodeInfo { Context = ctx };
|
||||||
|
|
||||||
|
@ -359,7 +383,7 @@ public class FrostFSClient : IFrostFSClient
|
||||||
if (isDisposed)
|
if (isDisposed)
|
||||||
throw new InvalidObjectException("Client is disposed.");
|
throw new InvalidObjectException("Client is disposed.");
|
||||||
|
|
||||||
ctx.Context ??= new Context();
|
ctx.Context ??= new CallContext();
|
||||||
|
|
||||||
if (ctx.Context.Key == null)
|
if (ctx.Context.Key == null)
|
||||||
{
|
{
|
||||||
|
@ -441,6 +465,16 @@ public class FrostFSClient : IFrostFSClient
|
||||||
return new ApeManagerServiceProvider(client, ClientCtx);
|
return new ApeManagerServiceProvider(client, ClientCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AccountingServiceProvider GetAccouningService(IContext ctx)
|
||||||
|
{
|
||||||
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
|
var client = AccountingServiceClient ?? (callInvoker != null
|
||||||
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
||||||
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
return new AccountingServiceProvider(client, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
private ContainerServiceProvider GetContainerService(IContext ctx)
|
private ContainerServiceProvider GetContainerService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ctx);
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
|
@ -461,6 +495,16 @@ public class FrostFSClient : IFrostFSClient
|
||||||
return new ObjectServiceProvider(client, ClientCtx);
|
return new ObjectServiceProvider(client, ClientCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AccountingServiceProvider GetAccountService(IContext ctx)
|
||||||
|
{
|
||||||
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
|
var client = AccountingServiceClient ?? (callInvoker != null
|
||||||
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
||||||
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
return new AccountingServiceProvider(client, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
|
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -480,4 +524,31 @@ public class FrostFSClient : IFrostFSClient
|
||||||
throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}");
|
throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string?> Dial(CallContext ctx)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prm = new PrmBalance { Context = ctx };
|
||||||
|
|
||||||
|
var service = GetAccouningService(prm);
|
||||||
|
var balance = await service.GetBallance(prm).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (FrostFsException ex)
|
||||||
|
{
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RestartIfUnhealthy(CallContext ctx)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
8
src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs
Normal file
8
src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
|
// attributes that are applied to this project.
|
||||||
|
// Project-level suppressions either have no target or are given
|
||||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "<Pending>", Scope = "member", Target = "~M:FrostFS.SDK.ClientV2.Sampler.Next~System.Int32")]
|
|
@ -19,7 +19,7 @@ public interface IFrostFSClient : IDisposable
|
||||||
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args);
|
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ApeMAnager
|
#region ApeManager
|
||||||
Task<byte[]> AddChainAsync(PrmApeChainAdd args);
|
Task<byte[]> AddChainAsync(PrmApeChainAdd args);
|
||||||
|
|
||||||
Task RemoveChainAsync(PrmApeChainRemove args);
|
Task RemoveChainAsync(PrmApeChainRemove args);
|
||||||
|
@ -51,7 +51,17 @@ public interface IFrostFSClient : IDisposable
|
||||||
IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args);
|
IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tools
|
#region Account
|
||||||
FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx);
|
Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args = null);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Tools
|
||||||
|
FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Task<string?> Dial(CallContext ctx);
|
||||||
|
|
||||||
|
public bool RestartIfUnhealthy(CallContext ctx);
|
||||||
|
|
||||||
|
public void Close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ public static class ContainerIdMapper
|
||||||
|
|
||||||
var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model));
|
var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model));
|
||||||
|
|
||||||
if (!Cache.Containers.TryGetValue(containerId, out ContainerID? message))
|
if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message))
|
||||||
{
|
{
|
||||||
message = new ContainerID
|
message = new ContainerID
|
||||||
{
|
{
|
||||||
Value = ByteString.CopyFrom(Base58.Decode(containerId))
|
Value = ByteString.CopyFrom(Base58.Decode(containerId))
|
||||||
};
|
};
|
||||||
|
|
||||||
Cache.Containers.Set(containerId, message, _oneHourExpiration);
|
Caches.Containers.Set(containerId, message, _oneHourExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message!;
|
return message!;
|
||||||
|
|
|
@ -22,14 +22,14 @@ public static class OwnerIdMapper
|
||||||
throw new ArgumentNullException(nameof(model));
|
throw new ArgumentNullException(nameof(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Cache.Owners.TryGetValue(model, out OwnerID? message))
|
if (!Caches.Owners.TryGetValue(model, out OwnerID? message))
|
||||||
{
|
{
|
||||||
message = new OwnerID
|
message = new OwnerID
|
||||||
{
|
{
|
||||||
Value = ByteString.CopyFrom(model.ToHash())
|
Value = ByteString.CopyFrom(model.ToHash())
|
||||||
};
|
};
|
||||||
|
|
||||||
Cache.Owners.Set(model, message, _oneHourExpiration);
|
Caches.Owners.Set(model, message, _oneHourExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message!;
|
return message!;
|
||||||
|
@ -42,11 +42,11 @@ public static class OwnerIdMapper
|
||||||
throw new ArgumentNullException(nameof(message));
|
throw new ArgumentNullException(nameof(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Cache.Owners.TryGetValue(message, out FrostFsOwner? model))
|
if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model))
|
||||||
{
|
{
|
||||||
model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray()));
|
model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray()));
|
||||||
|
|
||||||
Cache.Owners.Set(message, model, _oneHourExpiration);
|
Caches.Owners.Set(message, model, _oneHourExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
return model!;
|
return model!;
|
||||||
|
|
|
@ -2,62 +2,61 @@
|
||||||
|
|
||||||
using Frostfs.V2.Ape;
|
using Frostfs.V2.Ape;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable<FrostFsChainTarget>
|
||||||
{
|
{
|
||||||
public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable<FrostFsChainTarget>
|
private ChainTarget? chainTarget;
|
||||||
|
|
||||||
|
public FrostFsTargetType Type { get; } = type;
|
||||||
|
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
internal ChainTarget GetChainTarget()
|
||||||
{
|
{
|
||||||
private ChainTarget? chainTarget;
|
return chainTarget ??= new ChainTarget
|
||||||
|
|
||||||
public FrostFsTargetType Type { get; } = type;
|
|
||||||
|
|
||||||
public string Name { get; } = name;
|
|
||||||
|
|
||||||
internal ChainTarget GetChainTarget()
|
|
||||||
{
|
{
|
||||||
return chainTarget ??= new ChainTarget
|
Type = GetTargetType(Type),
|
||||||
{
|
Name = Name
|
||||||
Type = GetTargetType(Type),
|
};
|
||||||
Name = Name
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TargetType GetTargetType(FrostFsTargetType type)
|
private static TargetType GetTargetType(FrostFsTargetType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
{
|
{
|
||||||
return type switch
|
FrostFsTargetType.Undefined => TargetType.Undefined,
|
||||||
{
|
FrostFsTargetType.Namespace => TargetType.Namespace,
|
||||||
FrostFsTargetType.Undefined => TargetType.Undefined,
|
FrostFsTargetType.Container => TargetType.Container,
|
||||||
FrostFsTargetType.Namespace => TargetType.Namespace,
|
FrostFsTargetType.User => TargetType.User,
|
||||||
FrostFsTargetType.Container => TargetType.Container,
|
FrostFsTargetType.Group => TargetType.Group,
|
||||||
FrostFsTargetType.User => TargetType.User,
|
_ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)),
|
||||||
FrostFsTargetType.Group => TargetType.Group,
|
};
|
||||||
_ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)),
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly bool Equals(object obj)
|
public override readonly bool Equals(object obj)
|
||||||
{
|
{
|
||||||
var target = (FrostFsChainTarget)obj;
|
var target = (FrostFsChainTarget)obj;
|
||||||
return Equals(target);
|
return Equals(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override readonly int GetHashCode()
|
public override readonly int GetHashCode()
|
||||||
{
|
{
|
||||||
return $"{Name}{Type}".GetHashCode();
|
return $"{Name}{Type}".GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
|
public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
|
||||||
{
|
{
|
||||||
return left.Equals(right);
|
return left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right)
|
public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right)
|
||||||
{
|
{
|
||||||
return !(left == right);
|
return !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool Equals(FrostFsChainTarget other)
|
public readonly bool Equals(FrostFsChainTarget other)
|
||||||
{
|
{
|
||||||
return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
|
return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,41 @@
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
|
||||||
{
|
{
|
||||||
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
|
private ByteString? grpcRaw;
|
||||||
|
|
||||||
|
public byte[] Raw { get; } = raw;
|
||||||
|
|
||||||
|
internal ByteString GetRaw()
|
||||||
{
|
{
|
||||||
private ByteString? grpcRaw;
|
return grpcRaw ??= ByteString.CopyFrom(Raw);
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] Raw { get; } = raw;
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var chain = (FrostFsChain)obj;
|
||||||
|
return Equals(chain);
|
||||||
|
}
|
||||||
|
|
||||||
internal ByteString GetRaw()
|
public override readonly int GetHashCode()
|
||||||
{
|
{
|
||||||
return grpcRaw ??= ByteString.CopyFrom(Raw);
|
return Raw.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override readonly bool Equals(object obj)
|
public static bool operator ==(FrostFsChain left, FrostFsChain right)
|
||||||
{
|
{
|
||||||
var chain = (FrostFsChain)obj;
|
return left.Equals(right);
|
||||||
return Equals(chain);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly int GetHashCode()
|
public static bool operator !=(FrostFsChain left, FrostFsChain right)
|
||||||
{
|
{
|
||||||
return Raw.GetHashCode();
|
return !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(FrostFsChain left, FrostFsChain right)
|
public readonly bool Equals(FrostFsChain other)
|
||||||
{
|
{
|
||||||
return left.Equals(right);
|
return Raw == other.Raw;
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(FrostFsChain left, FrostFsChain right)
|
|
||||||
{
|
|
||||||
return !(left == right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly bool Equals(FrostFsChain other)
|
|
||||||
{
|
|
||||||
return Raw == other.Raw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
namespace FrostFS.SDK.ClientV2
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public enum FrostFsTargetType
|
||||||
{
|
{
|
||||||
public enum FrostFsTargetType
|
Undefined = 0,
|
||||||
{
|
Namespace,
|
||||||
Undefined = 0,
|
Container,
|
||||||
Namespace,
|
User,
|
||||||
Container,
|
Group
|
||||||
User,
|
|
||||||
Group
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace FrostFS.SDK;
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
public class FrostFsSessionToken(byte[] token)
|
public class FrostFsSessionToken(byte[] token, Guid id)
|
||||||
{
|
{
|
||||||
|
public Guid Id { get; private set; } = id;
|
||||||
public byte[] Token { get; private set; } = token;
|
public byte[] Token { get; private set; } = token;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class Context()
|
public class CallContext()
|
||||||
{
|
{
|
||||||
private ReadOnlyCollection<Interceptor>? interceptors;
|
private ReadOnlyCollection<Interceptor>? interceptors;
|
||||||
|
|
|
@ -7,5 +7,5 @@ public interface IContext
|
||||||
/// callbacks, interceptors.
|
/// callbacks, interceptors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Additional parameters for calling the method</value>
|
/// <value>Additional parameters for calling the method</value>
|
||||||
Context? Context { get; set; }
|
CallContext? Context { get; set; }
|
||||||
}
|
}
|
||||||
|
|
5
src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
Normal file
5
src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public sealed class PrmBalance() : PrmBase
|
||||||
|
{
|
||||||
|
}
|
|
@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext
|
||||||
public NameValueCollection XHeaders { get; } = xheaders ?? [];
|
public NameValueCollection XHeaders { get; } = xheaders ?? [];
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Context? Context { get; set; }
|
public CallContext? Context { get; set; }
|
||||||
}
|
}
|
||||||
|
|
165
src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
Normal file
165
src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// clientStatusMonitor count error rate and other statistics for connection.
|
||||||
|
public class ClientStatusMonitor : IClientStatus
|
||||||
|
{
|
||||||
|
private static readonly MethodIndex[] MethodIndexes =
|
||||||
|
[
|
||||||
|
MethodIndex.methodBalanceGet,
|
||||||
|
MethodIndex.methodContainerPut,
|
||||||
|
MethodIndex.methodContainerGet,
|
||||||
|
MethodIndex.methodContainerList,
|
||||||
|
MethodIndex.methodContainerDelete,
|
||||||
|
MethodIndex.methodEndpointInfo,
|
||||||
|
MethodIndex.methodNetworkInfo,
|
||||||
|
MethodIndex.methodNetMapSnapshot,
|
||||||
|
MethodIndex.methodObjectPut,
|
||||||
|
MethodIndex.methodObjectDelete,
|
||||||
|
MethodIndex.methodObjectGet,
|
||||||
|
MethodIndex.methodObjectHead,
|
||||||
|
MethodIndex.methodObjectRange,
|
||||||
|
MethodIndex.methodObjectPatch,
|
||||||
|
MethodIndex.methodSessionCreate,
|
||||||
|
MethodIndex.methodAPEManagerAddChain,
|
||||||
|
MethodIndex.methodAPEManagerRemoveChain,
|
||||||
|
MethodIndex.methodAPEManagerListChains,
|
||||||
|
MethodIndex.methodLast
|
||||||
|
];
|
||||||
|
|
||||||
|
public static string GetMethodName(MethodIndex index)
|
||||||
|
{
|
||||||
|
return index switch
|
||||||
|
{
|
||||||
|
MethodIndex.methodBalanceGet => "BalanceGet",
|
||||||
|
MethodIndex.methodContainerPut => "ContainerPut",
|
||||||
|
MethodIndex.methodContainerGet => "ContainerGet",
|
||||||
|
MethodIndex.methodContainerList => "ContainerList",
|
||||||
|
MethodIndex.methodContainerDelete => "ContainerDelete",
|
||||||
|
MethodIndex.methodEndpointInfo => "EndpointInfo",
|
||||||
|
MethodIndex.methodNetworkInfo => "NetworkInfo",
|
||||||
|
MethodIndex.methodNetMapSnapshot => "NetMapSnapshot",
|
||||||
|
MethodIndex.methodObjectPut => "ObjectPut",
|
||||||
|
MethodIndex.methodObjectDelete => "ObjectDelete",
|
||||||
|
MethodIndex.methodObjectGet => "ObjectGet",
|
||||||
|
MethodIndex.methodObjectHead => "ObjectHead",
|
||||||
|
MethodIndex.methodObjectRange => "ObjectRange",
|
||||||
|
MethodIndex.methodObjectPatch => "ObjectPatch",
|
||||||
|
MethodIndex.methodSessionCreate => "SessionCreate",
|
||||||
|
MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain",
|
||||||
|
MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain",
|
||||||
|
MethodIndex.methodAPEManagerListChains => "APEManagerListChains",
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
private readonly ILogger? logger;
|
||||||
|
private int healthy;
|
||||||
|
|
||||||
|
public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
healthy = (int)HealthyStatus.Healthy;
|
||||||
|
Address = address;
|
||||||
|
ErrorThreshold = errorThreshold;
|
||||||
|
|
||||||
|
Methods = new MethodStatus[MethodIndexes.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < MethodIndexes.Length; i++)
|
||||||
|
{
|
||||||
|
Methods[i] = new MethodStatus(GetMethodName(MethodIndexes[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Address { get; }
|
||||||
|
|
||||||
|
internal uint ErrorThreshold { get; }
|
||||||
|
|
||||||
|
public uint CurrentErrorCount { get; set; }
|
||||||
|
|
||||||
|
public ulong OverallErrorCount { get; set; }
|
||||||
|
|
||||||
|
public MethodStatus[] Methods { get; private set; }
|
||||||
|
|
||||||
|
public bool IsHealthy()
|
||||||
|
{
|
||||||
|
return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDialed()
|
||||||
|
{
|
||||||
|
return Interlocked.CompareExchange(ref healthy, -1, -1) != (int)HealthyStatus.UnhealthyOnDial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHealthy()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref healthy, (int)HealthyStatus.Healthy);
|
||||||
|
}
|
||||||
|
public void SetUnhealthy()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUnhealthyOnDial()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnDial);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void IncErrorRate()
|
||||||
|
{
|
||||||
|
bool thresholdReached;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
CurrentErrorCount++;
|
||||||
|
OverallErrorCount++;
|
||||||
|
|
||||||
|
thresholdReached = CurrentErrorCount >= ErrorThreshold;
|
||||||
|
|
||||||
|
if (thresholdReached)
|
||||||
|
{
|
||||||
|
SetUnhealthy();
|
||||||
|
|
||||||
|
CurrentErrorCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thresholdReached)
|
||||||
|
{
|
||||||
|
logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetCurrentErrorRate()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return CurrentErrorCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetOverallErrorRate()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return OverallErrorCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusSnapshot[] MethodsStatus()
|
||||||
|
{
|
||||||
|
var result = new StatusSnapshot[Methods.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < result.Length; i++)
|
||||||
|
{
|
||||||
|
result[i] = Methods[i].Snapshot!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
137
src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
Normal file
137
src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// clientWrapper is used by default, alternative implementations are intended for testing purposes only.
|
||||||
|
public class ClientWrapper
|
||||||
|
{
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
public ClientWrapper(WrapperPrm wrapperPrm)
|
||||||
|
{
|
||||||
|
WrapperPrm = wrapperPrm;
|
||||||
|
StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Client = new FrostFSClient(WrapperPrm);
|
||||||
|
StatusMonitor.SetHealthy();
|
||||||
|
}
|
||||||
|
catch (FrostFsException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFSClient? Client { get; private set; }
|
||||||
|
|
||||||
|
internal WrapperPrm WrapperPrm { get; }
|
||||||
|
|
||||||
|
internal ClientStatusMonitor StatusMonitor { get; }
|
||||||
|
|
||||||
|
internal FrostFSClient? GetClient()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (StatusMonitor.IsHealthy())
|
||||||
|
{
|
||||||
|
return Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial establishes a connection to the server from the FrostFS network.
|
||||||
|
// Returns an error describing failure reason. If failed, the client
|
||||||
|
// SHOULD NOT be used.
|
||||||
|
internal async Task<string?> Dial(CallContext ctx)
|
||||||
|
{
|
||||||
|
var client = GetClient();
|
||||||
|
|
||||||
|
if (client == null)
|
||||||
|
return "pool client unhealthy";
|
||||||
|
|
||||||
|
var result = await client.Dial(ctx).ConfigureAwait(false);
|
||||||
|
if (!string.IsNullOrEmpty(result))
|
||||||
|
{
|
||||||
|
StatusMonitor.SetUnhealthyOnDial();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ScheduleGracefulClose()
|
||||||
|
{
|
||||||
|
if (Client == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Task.Delay((int)WrapperPrm.GracefulCloseOnSwitchTimeout).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Client.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy.
|
||||||
|
// Indicating if status was changed by this function call and returns error that caused unhealthy status.
|
||||||
|
internal async Task<bool> RestartIfUnhealthy(CallContext ctx)
|
||||||
|
{
|
||||||
|
bool wasHealthy;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
|
||||||
|
var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (FrostFsException)
|
||||||
|
{
|
||||||
|
wasHealthy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if connection is dialed before, to avoid routine/connection leak,
|
||||||
|
// pool has to close it and then initialize once again.
|
||||||
|
if (StatusMonitor.IsDialed())
|
||||||
|
{
|
||||||
|
await ScheduleGracefulClose().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
|
||||||
|
FrostFSClient client = new(WrapperPrm);
|
||||||
|
#pragma warning restore CA2000
|
||||||
|
|
||||||
|
//TODO: set additioanl params
|
||||||
|
var error = await client.Dial(ctx).ConfigureAwait(false);
|
||||||
|
if (!string.IsNullOrEmpty(error))
|
||||||
|
{
|
||||||
|
StatusMonitor.SetUnhealthyOnDial();
|
||||||
|
return wasHealthy;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prmNodeInfo = new PrmNodeInfo { Context = ctx };
|
||||||
|
var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (FrostFsException)
|
||||||
|
{
|
||||||
|
StatusMonitor.SetUnhealthy();
|
||||||
|
return wasHealthy;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusMonitor.SetHealthy();
|
||||||
|
return !wasHealthy;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void IncRequests(ulong elapsed, MethodIndex method)
|
||||||
|
{
|
||||||
|
var methodStat = StatusMonitor.Methods[(int)method];
|
||||||
|
|
||||||
|
methodStat.IncRequests(elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
Normal file
16
src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class DialOptions
|
||||||
|
{
|
||||||
|
public bool Block { get; set; }
|
||||||
|
|
||||||
|
public bool ReturnLastError { get; set; }
|
||||||
|
|
||||||
|
public ulong Timeout { get; set; }
|
||||||
|
|
||||||
|
public string? Authority { get; set; }
|
||||||
|
|
||||||
|
public bool DisableRetry { get; set; }
|
||||||
|
|
||||||
|
public bool DisableHealthCheck { get; set; }
|
||||||
|
}
|
18
src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs
Normal file
18
src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// values for healthy status of clientStatusMonitor.
|
||||||
|
public enum HealthyStatus
|
||||||
|
{
|
||||||
|
// statusUnhealthyOnDial is set when dialing to the endpoint is failed,
|
||||||
|
// so there is no connection to the endpoint, and pool should not close it
|
||||||
|
// before re-establishing connection once again.
|
||||||
|
UnhealthyOnDial,
|
||||||
|
|
||||||
|
// statusUnhealthyOnRequest is set when communication after dialing to the
|
||||||
|
// endpoint is failed due to immediate or accumulated errors, connection is
|
||||||
|
// available and pool should close it before re-establishing connection once again.
|
||||||
|
UnhealthyOnRequest,
|
||||||
|
|
||||||
|
// statusHealthy is set when connection is ready to be used by the pool.
|
||||||
|
Healthy
|
||||||
|
}
|
28
src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs
Normal file
28
src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public interface IClientStatus
|
||||||
|
{
|
||||||
|
// isHealthy checks if the connection can handle requests.
|
||||||
|
bool IsHealthy();
|
||||||
|
|
||||||
|
// isDialed checks if the connection was created.
|
||||||
|
bool IsDialed();
|
||||||
|
|
||||||
|
// setUnhealthy marks client as unhealthy.
|
||||||
|
void SetUnhealthy();
|
||||||
|
|
||||||
|
// address return address of endpoint.
|
||||||
|
string Address { get; }
|
||||||
|
|
||||||
|
// currentErrorRate returns current errors rate.
|
||||||
|
// After specific threshold connection is considered as unhealthy.
|
||||||
|
// Pool.startRebalance routine can make this connection healthy again.
|
||||||
|
uint GetCurrentErrorRate();
|
||||||
|
|
||||||
|
// overallErrorRate returns the number of all happened errors.
|
||||||
|
ulong GetOverallErrorRate();
|
||||||
|
|
||||||
|
// methodsStatus returns statistic for all used methods.
|
||||||
|
StatusSnapshot[] MethodsStatus();
|
||||||
|
}
|
||||||
|
|
34
src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
Normal file
34
src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// InitParameters contains values used to initialize connection Pool.
|
||||||
|
public class InitParameters
|
||||||
|
{
|
||||||
|
public ECDsa? Key { get; set; }
|
||||||
|
|
||||||
|
public ulong NodeDialTimeout { get; set; }
|
||||||
|
|
||||||
|
public ulong NodeStreamTimeout { get; set; }
|
||||||
|
|
||||||
|
public ulong HealthcheckTimeout { get; set; }
|
||||||
|
|
||||||
|
public ulong ClientRebalanceInterval { get; set; }
|
||||||
|
|
||||||
|
public ulong SessionExpirationDuration { get; set; }
|
||||||
|
|
||||||
|
public uint ErrorThreshold { get; set; }
|
||||||
|
|
||||||
|
public NodeParam[]? NodeParams { get; set; }
|
||||||
|
|
||||||
|
public DialOptions[]? DialOptions { get; set; }
|
||||||
|
|
||||||
|
public Func<string, ClientWrapper>? ClientBuilder { get; set; }
|
||||||
|
|
||||||
|
public ulong GracefulCloseOnSwitchTimeout { get; set; }
|
||||||
|
|
||||||
|
public ILogger? Logger { get; set; }
|
||||||
|
}
|
47
src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
Normal file
47
src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal sealed class InnerPool
|
||||||
|
{
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
internal InnerPool(Sampler sampler, ClientWrapper[] clients)
|
||||||
|
{
|
||||||
|
Clients = clients;
|
||||||
|
Sampler = sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Sampler Sampler { get; set; }
|
||||||
|
|
||||||
|
internal ClientWrapper[] Clients { get; }
|
||||||
|
|
||||||
|
internal ClientWrapper? Connection()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (Clients.Length == 1)
|
||||||
|
{
|
||||||
|
var client = Clients[0];
|
||||||
|
if (client.StatusMonitor.IsHealthy())
|
||||||
|
{
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var attempts = 3 * Clients.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < attempts; i++)
|
||||||
|
{
|
||||||
|
int index = Sampler.Next();
|
||||||
|
|
||||||
|
if (Clients[index].StatusMonitor.IsHealthy())
|
||||||
|
{
|
||||||
|
return Clients[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs
Normal file
24
src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public enum MethodIndex
|
||||||
|
{
|
||||||
|
methodBalanceGet,
|
||||||
|
methodContainerPut,
|
||||||
|
methodContainerGet,
|
||||||
|
methodContainerList,
|
||||||
|
methodContainerDelete,
|
||||||
|
methodEndpointInfo,
|
||||||
|
methodNetworkInfo,
|
||||||
|
methodNetMapSnapshot,
|
||||||
|
methodObjectPut,
|
||||||
|
methodObjectDelete,
|
||||||
|
methodObjectGet,
|
||||||
|
methodObjectHead,
|
||||||
|
methodObjectRange,
|
||||||
|
methodObjectPatch,
|
||||||
|
methodSessionCreate,
|
||||||
|
methodAPEManagerAddChain,
|
||||||
|
methodAPEManagerRemoveChain,
|
||||||
|
methodAPEManagerListChains,
|
||||||
|
methodLast
|
||||||
|
}
|
19
src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs
Normal file
19
src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class MethodStatus(string name)
|
||||||
|
{
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
public StatusSnapshot Snapshot { get; set; } = new StatusSnapshot();
|
||||||
|
|
||||||
|
internal void IncRequests(ulong elapsed)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Snapshot.AllTime += elapsed;
|
||||||
|
Snapshot.AllRequests++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs
Normal file
12
src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// NodeParam groups parameters of remote node.
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "<Pending>")]
|
||||||
|
public readonly struct NodeParam(int priority, string address, float weight)
|
||||||
|
{
|
||||||
|
public int Priority { get; } = priority;
|
||||||
|
|
||||||
|
public string Address { get; } = address;
|
||||||
|
|
||||||
|
public float Weight { get; } = weight;
|
||||||
|
}
|
12
src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs
Normal file
12
src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class NodeStatistic
|
||||||
|
{
|
||||||
|
public string? Address { get; internal set; }
|
||||||
|
|
||||||
|
public StatusSnapshot[]? Methods { get; internal set; }
|
||||||
|
|
||||||
|
public ulong OverallErrors { get; internal set; }
|
||||||
|
|
||||||
|
public uint CurrentErrors { get; internal set; }
|
||||||
|
}
|
12
src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs
Normal file
12
src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class NodesParam(int priority)
|
||||||
|
{
|
||||||
|
public int Priority { get; } = priority;
|
||||||
|
|
||||||
|
public Collection<string> Addresses { get; } = [];
|
||||||
|
|
||||||
|
public Collection<double> Weights { get; } = [];
|
||||||
|
}
|
651
src/FrostFS.SDK.ClientV2/Poll/Pool.cs
Normal file
651
src/FrostFS.SDK.ClientV2/Poll/Pool.cs
Normal file
|
@ -0,0 +1,651 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Frostfs.V2.Ape;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public partial class Pool : IFrostFSClient
|
||||||
|
{
|
||||||
|
const int defaultSessionTokenExpirationDuration = 100; // in epochs
|
||||||
|
|
||||||
|
const int defaultErrorThreshold = 100;
|
||||||
|
|
||||||
|
const int defaultGracefulCloseOnSwitchTimeout = 10; //Seconds;
|
||||||
|
const int defaultRebalanceInterval = 15; //Seconds;
|
||||||
|
const int defaultHealthcheckTimeout = 4; //Seconds;
|
||||||
|
const int defaultDialTimeout = 5; //Seconds;
|
||||||
|
const int defaultStreamTimeout = 10; //Seconds;
|
||||||
|
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
private InnerPool[]? InnerPools { get; set; }
|
||||||
|
|
||||||
|
private ECDsa Key { get; set; }
|
||||||
|
|
||||||
|
private byte[] PublicKey { get; }
|
||||||
|
|
||||||
|
private OwnerID? _ownerId;
|
||||||
|
private FrostFsOwner? _owner;
|
||||||
|
|
||||||
|
private FrostFsOwner Owner
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_owner ??= new FrostFsOwner(Key.PublicKey().PublicKeyToAddress());
|
||||||
|
return _owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OwnerID OwnerId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_ownerId == null)
|
||||||
|
{
|
||||||
|
_owner = new FrostFsOwner(Key.PublicKey().PublicKeyToAddress());
|
||||||
|
_ownerId = _owner.ToMessage();
|
||||||
|
}
|
||||||
|
return _ownerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
|
||||||
|
|
||||||
|
private SessionCache Cache { get; set; }
|
||||||
|
|
||||||
|
private ulong SessionTokenDuration { get; set; }
|
||||||
|
|
||||||
|
private RebalanceParameters RebalanceParams { get; set; }
|
||||||
|
|
||||||
|
private Func<string, ClientWrapper> ClientBuilder;
|
||||||
|
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
private ILogger? Logger { get; set; }
|
||||||
|
|
||||||
|
private ulong MaxObjectSize { get; set; }
|
||||||
|
|
||||||
|
public IClientStatus? ClientStatus { get; }
|
||||||
|
|
||||||
|
// NewPool creates connection pool using parameters.
|
||||||
|
public Pool(InitParameters options)
|
||||||
|
{
|
||||||
|
if (options is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.Key == null)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"Missed required parameter {nameof(options.Key)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodesParams = AdjustNodeParams(options.NodeParams);
|
||||||
|
|
||||||
|
var cache = new SessionCache(options.SessionExpirationDuration);
|
||||||
|
|
||||||
|
FillDefaultInitParams(options, cache);
|
||||||
|
|
||||||
|
Key = options.Key;
|
||||||
|
PublicKey = Key.PublicKey();
|
||||||
|
|
||||||
|
Cache = cache;
|
||||||
|
Logger = options.Logger;
|
||||||
|
SessionTokenDuration = options.SessionExpirationDuration;
|
||||||
|
|
||||||
|
RebalanceParams = new RebalanceParameters(
|
||||||
|
nodesParams.ToArray(),
|
||||||
|
options.HealthcheckTimeout,
|
||||||
|
options.ClientRebalanceInterval,
|
||||||
|
options.SessionExpirationDuration);
|
||||||
|
|
||||||
|
ClientBuilder = options.ClientBuilder!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupContext(CallContext ctx)
|
||||||
|
{
|
||||||
|
if (ctx == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Key ??= Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial establishes a connection to the servers from the FrostFS network.
|
||||||
|
// It also starts a routine that checks the health of the nodes and
|
||||||
|
// updates the weights of the nodes for balancing.
|
||||||
|
// Returns an error describing failure reason.
|
||||||
|
//
|
||||||
|
// If failed, the Pool SHOULD NOT be used.
|
||||||
|
//
|
||||||
|
// See also InitParameters.SetClientRebalanceInterval.
|
||||||
|
public async Task<string?> Dial(CallContext ctx)
|
||||||
|
{
|
||||||
|
SetupContext(ctx);
|
||||||
|
|
||||||
|
var inner = new InnerPool[RebalanceParams.NodesParams.Length];
|
||||||
|
|
||||||
|
bool atLeastOneHealthy = false;
|
||||||
|
int i = 0;
|
||||||
|
foreach (var nodeParams in RebalanceParams.NodesParams)
|
||||||
|
{
|
||||||
|
var clients = new ClientWrapper[nodeParams.Weights.Count];
|
||||||
|
|
||||||
|
for (int j = 0; j < nodeParams.Addresses.Count; j++)
|
||||||
|
{
|
||||||
|
var client = ClientBuilder(nodeParams.Addresses[j]);
|
||||||
|
clients[j] = client;
|
||||||
|
|
||||||
|
var error = await client.Dial(ctx).ConfigureAwait(false);
|
||||||
|
if (!string.IsNullOrEmpty(error))
|
||||||
|
{
|
||||||
|
Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var key = FormCacheKey(nodeParams.Addresses[j], Key, false);
|
||||||
|
_ = Cache.Cache[key] = token;
|
||||||
|
}
|
||||||
|
catch (FrostFsException ex)
|
||||||
|
{
|
||||||
|
client.StatusMonitor.SetUnhealthy();
|
||||||
|
Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})",
|
||||||
|
client.WrapperPrm.Address, ex.Message);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
atLeastOneHealthy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampler = new Sampler(nodeParams.Weights.ToArray());
|
||||||
|
|
||||||
|
inner[i] = new InnerPool(sampler, clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!atLeastOneHealthy)
|
||||||
|
return "at least one node must be healthy";
|
||||||
|
|
||||||
|
InnerPools = inner;
|
||||||
|
|
||||||
|
var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
|
||||||
|
|
||||||
|
MaxObjectSize = res.MaxObjectSize;
|
||||||
|
|
||||||
|
StartRebalance(ctx);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<NodesParam> AdjustNodeParams(NodeParam[]? nodeParams)
|
||||||
|
{
|
||||||
|
if (nodeParams == null || nodeParams.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("No FrostFS peers configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<int, NodesParam> nodesParamsDict = new(nodeParams.Length);
|
||||||
|
foreach (var nodeParam in nodeParams)
|
||||||
|
{
|
||||||
|
if (!nodesParamsDict.TryGetValue(nodeParam.Priority, out var nodes))
|
||||||
|
{
|
||||||
|
nodes = new NodesParam(nodeParam.Priority);
|
||||||
|
nodesParamsDict[nodeParam.Priority] = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.Addresses.Add(nodeParam.Address);
|
||||||
|
nodes.Weights.Add(nodeParam.Weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodesParams = new List<NodesParam>(nodesParamsDict.Count);
|
||||||
|
|
||||||
|
foreach (var key in nodesParamsDict.Keys)
|
||||||
|
{
|
||||||
|
var nodes = nodesParamsDict[key];
|
||||||
|
var newWeights = AdjustWeights([.. nodes.Weights]);
|
||||||
|
nodes.Weights.Clear();
|
||||||
|
foreach (var weight in newWeights)
|
||||||
|
{
|
||||||
|
nodes.Weights.Add(weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesParams.Add(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodesParams.OrderBy(n => n.Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double[] AdjustWeights(double[] weights)
|
||||||
|
{
|
||||||
|
var adjusted = new double[weights.Length];
|
||||||
|
|
||||||
|
var sum = weights.Sum();
|
||||||
|
|
||||||
|
if (sum > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < weights.Length; i++)
|
||||||
|
{
|
||||||
|
adjusted[i] = weights[i] / sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache)
|
||||||
|
{
|
||||||
|
if (parameters.SessionExpirationDuration == 0)
|
||||||
|
parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration;
|
||||||
|
|
||||||
|
if (parameters.ErrorThreshold == 0)
|
||||||
|
parameters.ErrorThreshold = defaultErrorThreshold;
|
||||||
|
|
||||||
|
if (parameters.ClientRebalanceInterval <= 0)
|
||||||
|
parameters.ClientRebalanceInterval = defaultRebalanceInterval;
|
||||||
|
|
||||||
|
if (parameters.GracefulCloseOnSwitchTimeout <= 0)
|
||||||
|
parameters.GracefulCloseOnSwitchTimeout = defaultGracefulCloseOnSwitchTimeout;
|
||||||
|
|
||||||
|
if (parameters.HealthcheckTimeout <= 0)
|
||||||
|
parameters.HealthcheckTimeout = defaultHealthcheckTimeout;
|
||||||
|
|
||||||
|
if (parameters.NodeDialTimeout <= 0)
|
||||||
|
parameters.NodeDialTimeout = defaultDialTimeout;
|
||||||
|
|
||||||
|
if (parameters.NodeStreamTimeout <= 0)
|
||||||
|
parameters.NodeStreamTimeout = defaultStreamTimeout;
|
||||||
|
|
||||||
|
if (cache.TokenDuration == 0)
|
||||||
|
cache.TokenDuration = defaultSessionTokenExpirationDuration;
|
||||||
|
|
||||||
|
parameters.ClientBuilder ??= new Func<string, ClientWrapper>((address) =>
|
||||||
|
{
|
||||||
|
var wrapperPrm = new WrapperPrm
|
||||||
|
{
|
||||||
|
Address = address,
|
||||||
|
Key = parameters.Key,
|
||||||
|
Logger = parameters.Logger,
|
||||||
|
DialTimeout = parameters.NodeDialTimeout,
|
||||||
|
StreamTimeout = parameters.NodeStreamTimeout,
|
||||||
|
ErrorThreshold = parameters.ErrorThreshold,
|
||||||
|
GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout
|
||||||
|
};
|
||||||
|
|
||||||
|
return new ClientWrapper(wrapperPrm);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrostFSClient? Сonnection()
|
||||||
|
{
|
||||||
|
foreach (var pool in InnerPools!)
|
||||||
|
{
|
||||||
|
var client = pool.Connection();
|
||||||
|
if (client != null)
|
||||||
|
{
|
||||||
|
return client.Client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<FrostFsSessionToken?> InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut)
|
||||||
|
{
|
||||||
|
var client = cw.Client;
|
||||||
|
var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var epoch = networkInfo.Epoch;
|
||||||
|
|
||||||
|
ulong exp = ulong.MaxValue - epoch < duration
|
||||||
|
? ulong.MaxValue
|
||||||
|
: epoch + duration;
|
||||||
|
|
||||||
|
var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx };
|
||||||
|
|
||||||
|
return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormCacheKey(string address, ECDsa key, bool clientCut)
|
||||||
|
{
|
||||||
|
var k = key.PrivateKey;
|
||||||
|
var stype = clientCut ? "client" : "server";
|
||||||
|
|
||||||
|
return $"{address}{stype}{k}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
CancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
if (InnerPools != null)
|
||||||
|
{
|
||||||
|
// close all clients
|
||||||
|
foreach (var innerPool in InnerPools)
|
||||||
|
foreach (var client in innerPool.Clients)
|
||||||
|
if (client.StatusMonitor.IsDialed())
|
||||||
|
client.Client?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startRebalance runs loop to monitor connection healthy status.
|
||||||
|
internal void StartRebalance(CallContext ctx)
|
||||||
|
{
|
||||||
|
var buffers = new double[RebalanceParams.NodesParams.Length][];
|
||||||
|
|
||||||
|
for (int i = 0; i < RebalanceParams.NodesParams.Length; i++)
|
||||||
|
{
|
||||||
|
var parameters = this.RebalanceParams.NodesParams[i];
|
||||||
|
buffers[i] = new double[parameters.Weights.Count];
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay((int)RebalanceParams.ClientRebalanceInterval).ConfigureAwait(false);
|
||||||
|
UpdateNodesHealth(ctx, buffers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNodesHealth(CallContext ctx, double[][] buffers)
|
||||||
|
{
|
||||||
|
var tasks = new Task[InnerPools!.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < InnerPools.Length; i++)
|
||||||
|
{
|
||||||
|
var bufferWeights = buffers[i];
|
||||||
|
|
||||||
|
tasks[i] = Task.Run(() => UpdateInnerNodesHealth(ctx, i, bufferWeights));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask UpdateInnerNodesHealth(CallContext ctx, int poolIndex, double[] bufferWeights)
|
||||||
|
{
|
||||||
|
if (poolIndex > InnerPools!.Length - 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pool = InnerPools[poolIndex];
|
||||||
|
|
||||||
|
var options = RebalanceParams;
|
||||||
|
|
||||||
|
int healthyChanged = 0;
|
||||||
|
|
||||||
|
var tasks = new Task[pool.Clients.Length];
|
||||||
|
|
||||||
|
for (int j = 0; j < pool.Clients.Length; j++)
|
||||||
|
{
|
||||||
|
var client = pool.Clients[j];
|
||||||
|
var healthy = false;
|
||||||
|
string? error = null;
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// check timeout settings
|
||||||
|
changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false);
|
||||||
|
healthy = true;
|
||||||
|
bufferWeights[j] = options.NodesParams[poolIndex].Weights[j];
|
||||||
|
}
|
||||||
|
catch (FrostFsException e)
|
||||||
|
{
|
||||||
|
error = e.Message;
|
||||||
|
bufferWeights[j] = 0;
|
||||||
|
|
||||||
|
Cache.DeleteByPrefix(client.StatusMonitor.Address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}");
|
||||||
|
if (string.IsNullOrEmpty(error))
|
||||||
|
{
|
||||||
|
fields.Append($", reason {error}");
|
||||||
|
Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString());
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref healthyChanged, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (Interlocked.CompareExchange(ref healthyChanged, -1, -1) == 1)
|
||||||
|
{
|
||||||
|
var probabilities = AdjustWeights(bufferWeights);
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
pool.Sampler = new Sampler(probabilities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckSessionTokenErr(Exception error, string address)
|
||||||
|
{
|
||||||
|
if (error == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error is SessionNotFoundException || error is SessionExpiredException)
|
||||||
|
{
|
||||||
|
this.Cache.DeleteByPrefix(address);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Statistic Statistic()
|
||||||
|
{
|
||||||
|
if (InnerPools == null)
|
||||||
|
{
|
||||||
|
throw new InvalidObjectException(nameof(Pool));
|
||||||
|
}
|
||||||
|
|
||||||
|
var statistics = new Statistic();
|
||||||
|
|
||||||
|
foreach (var inner in InnerPools)
|
||||||
|
{
|
||||||
|
int nodeIndex = 0;
|
||||||
|
int valueIndex = 0;
|
||||||
|
var nodes = new string[inner.Clients.Length];
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
foreach (var client in inner.Clients)
|
||||||
|
{
|
||||||
|
if (client.StatusMonitor.IsHealthy())
|
||||||
|
{
|
||||||
|
nodes[valueIndex++] = client.StatusMonitor.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = new NodeStatistic
|
||||||
|
{
|
||||||
|
Address = client.StatusMonitor.Address,
|
||||||
|
Methods = client.StatusMonitor.MethodsStatus(),
|
||||||
|
OverallErrors = client.StatusMonitor.GetOverallErrorRate(),
|
||||||
|
CurrentErrors = client.StatusMonitor.GetCurrentErrorRate()
|
||||||
|
};
|
||||||
|
|
||||||
|
statistics.Nodes[nodeIndex++] = node;
|
||||||
|
|
||||||
|
statistics.OverallErrors += node.OverallErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statistics.CurrentNodes == null || statistics.CurrentNodes.Length == 0)
|
||||||
|
{
|
||||||
|
statistics.CurrentNodes = nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args = null)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetNodeInfoAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args = null)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.CreateSessionAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.AddChainAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveChainAsync(PrmApeChainRemove args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
await client.RemoveChainAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.ListChainAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetContainerAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return client.ListContainersAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.CreateContainerAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteContainerAsync(PrmContainerDelete args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
await client.DeleteContainerAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetObjectHeadAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsObject> GetObjectAsync(PrmObjectGet args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetObjectAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.PutObjectAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.PutSingleObjectAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
await client.DeleteObjectAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return client.SearchObjectsAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args = null)
|
||||||
|
{
|
||||||
|
var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client");
|
||||||
|
return await client.GetBalanceAsync(args).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RestartIfUnhealthy(CallContext ctx)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHealthy()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
16
src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
Normal file
16
src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class RebalanceParameters(
|
||||||
|
NodesParam[] nodesParams,
|
||||||
|
ulong nodeRequestTimeout,
|
||||||
|
ulong clientRebalanceInterval,
|
||||||
|
ulong sessionExpirationDuration)
|
||||||
|
{
|
||||||
|
public NodesParam[] NodesParams { get; set; } = nodesParams;
|
||||||
|
|
||||||
|
public ulong NodeRequestTimeout { get; set; } = nodeRequestTimeout;
|
||||||
|
|
||||||
|
public ulong ClientRebalanceInterval { get; set; } = clientRebalanceInterval;
|
||||||
|
|
||||||
|
public ulong SessionExpirationDuration { get; set; } = sessionExpirationDuration;
|
||||||
|
}
|
14
src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs
Normal file
14
src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// RequestInfo groups info about pool request.
|
||||||
|
struct RequestInfo
|
||||||
|
{
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
public MethodIndex MethodIndex { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan Elapsed { get; set; }
|
||||||
|
}
|
||||||
|
|
85
src/FrostFS.SDK.ClientV2/Poll/Sampler.cs
Normal file
85
src/FrostFS.SDK.ClientV2/Poll/Sampler.cs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal sealed class Sampler
|
||||||
|
{
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
private Random random = new();
|
||||||
|
|
||||||
|
internal double[] Probabilities { get; set; }
|
||||||
|
internal int[] Alias { get; set; }
|
||||||
|
|
||||||
|
internal Sampler(double[] probabilities)
|
||||||
|
{
|
||||||
|
var small = new WorkList();
|
||||||
|
var large = new WorkList();
|
||||||
|
|
||||||
|
var n = probabilities.Length;
|
||||||
|
|
||||||
|
// sampler.randomGenerator = rand.New(source)
|
||||||
|
Probabilities = new double[n];
|
||||||
|
Alias = new int[n];
|
||||||
|
|
||||||
|
// Compute scaled probabilities.
|
||||||
|
var p = new double[n];
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
p[i] = probabilities[i] * n;
|
||||||
|
if (p[i] < 1)
|
||||||
|
small.Add(i);
|
||||||
|
else
|
||||||
|
large.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (small.Length > 0 && large.Length > 0)
|
||||||
|
{
|
||||||
|
var l = small.Remove();
|
||||||
|
var g = large.Remove();
|
||||||
|
|
||||||
|
Probabilities[l] = p[l];
|
||||||
|
Alias[l] = g;
|
||||||
|
|
||||||
|
p[g] = p[g] + p[l] - 1;
|
||||||
|
|
||||||
|
if (p[g] < 1)
|
||||||
|
small.Add(g);
|
||||||
|
else
|
||||||
|
large.Add(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (large.Length > 0)
|
||||||
|
{
|
||||||
|
var g = large.Remove();
|
||||||
|
Probabilities[g] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (small.Length > 0)
|
||||||
|
{
|
||||||
|
var l = small.Remove();
|
||||||
|
probabilities[l] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int Next()
|
||||||
|
{
|
||||||
|
var n = Alias.Length;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
double f;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
i = random.Next(0, n - 1);
|
||||||
|
f = random.NextDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f < Probabilities[i])
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Alias[i];
|
||||||
|
}
|
||||||
|
}
|
29
src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
Normal file
29
src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal struct SessionCache
|
||||||
|
{
|
||||||
|
public SessionCache(ulong sessionExpirationDuration)
|
||||||
|
{
|
||||||
|
TokenDuration = sessionExpirationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Hashtable Cache { get; } = [];
|
||||||
|
|
||||||
|
internal ulong CurrentEpoch { get; set; }
|
||||||
|
|
||||||
|
internal ulong TokenDuration { get; set; }
|
||||||
|
|
||||||
|
internal void DeleteByPrefix(string prefix)
|
||||||
|
{
|
||||||
|
foreach (var key in Cache.Keys)
|
||||||
|
{
|
||||||
|
if (((string)key).StartsWith(prefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Cache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/FrostFS.SDK.ClientV2/Poll/Statistic.cs
Normal file
12
src/FrostFS.SDK.ClientV2/Poll/Statistic.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public sealed class Statistic
|
||||||
|
{
|
||||||
|
public ulong OverallErrors { get; internal set; }
|
||||||
|
|
||||||
|
public Collection<NodeStatistic> Nodes { get; } = [];
|
||||||
|
|
||||||
|
public string[]? CurrentNodes { get; internal set; }
|
||||||
|
}
|
8
src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs
Normal file
8
src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
public class StatusSnapshot()
|
||||||
|
{
|
||||||
|
public ulong AllTime { get; internal set; }
|
||||||
|
|
||||||
|
public ulong AllRequests { get; internal set; }
|
||||||
|
}
|
26
src/FrostFS.SDK.ClientV2/Poll/WorkList.cs
Normal file
26
src/FrostFS.SDK.ClientV2/Poll/WorkList.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal sealed class WorkList
|
||||||
|
{
|
||||||
|
private readonly List<int> elements = [];
|
||||||
|
|
||||||
|
internal int Length
|
||||||
|
{
|
||||||
|
get { return elements.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Add(int element)
|
||||||
|
{
|
||||||
|
elements.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int Remove()
|
||||||
|
{
|
||||||
|
int last = elements.LastOrDefault();
|
||||||
|
elements.RemoveAt(elements.Count - 1);
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
}
|
34
src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs
Normal file
34
src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
// wrapperPrm is params to create clientWrapper.
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "<Pending>")]
|
||||||
|
public struct WrapperPrm
|
||||||
|
{
|
||||||
|
internal ILogger? Logger { get; set; }
|
||||||
|
|
||||||
|
internal string Address { get; set; }
|
||||||
|
|
||||||
|
internal ECDsa? Key { get; set; }
|
||||||
|
|
||||||
|
internal ulong DialTimeout { get; set; }
|
||||||
|
|
||||||
|
internal ulong StreamTimeout { get; set; }
|
||||||
|
|
||||||
|
internal uint ErrorThreshold { get; set; }
|
||||||
|
|
||||||
|
internal Action ResponseInfoCallback { get; set; }
|
||||||
|
|
||||||
|
internal Action PoolRequestInfoCallback { get; set; }
|
||||||
|
|
||||||
|
internal GrpcChannelOptions GrpcChannelOptions { get; set; }
|
||||||
|
|
||||||
|
internal ulong GracefulCloseOnSwitchTimeout { get; set; }
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FrostFS.Accounting;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal sealed class AccountingServiceProvider : ContextAccessor
|
||||||
|
{
|
||||||
|
private readonly AccountingService.AccountingServiceClient? _accountingServiceClient;
|
||||||
|
|
||||||
|
internal AccountingServiceProvider(
|
||||||
|
AccountingService.AccountingServiceClient? accountingServiceClient,
|
||||||
|
EnvironmentContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
_accountingServiceClient = accountingServiceClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<Decimal> GetBallance(PrmBalance args)
|
||||||
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
|
|
||||||
|
BalanceRequest request = new()
|
||||||
|
{
|
||||||
|
Body = new()
|
||||||
|
{
|
||||||
|
OwnerId = ctx.OwnerId!.OwnerID
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.AddMetaHeader(args.XHeaders);
|
||||||
|
request.Sign(ctx.Key!);
|
||||||
|
|
||||||
|
var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
return response.Body.Balance;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,13 @@ using System.Threading.Tasks;
|
||||||
using Frostfs.V2.Ape;
|
using Frostfs.V2.Ape;
|
||||||
using Frostfs.V2.Apemanager;
|
using Frostfs.V2.Apemanager;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2.Services;
|
||||||
|
|
||||||
internal sealed class ApeManagerServiceProvider : ContextAccessor
|
internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
|
private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient;
|
||||||
|
|
||||||
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientEnvironment context)
|
internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_apeManagerServiceClient = apeManagerServiceClient;
|
_apeManagerServiceClient = apeManagerServiceClient;
|
||||||
|
@ -18,7 +18,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
internal async Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -45,7 +45,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task RemoveChainAsync(PrmApeChainRemove args)
|
internal async Task RemoveChainAsync(PrmApeChainRemove args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -70,7 +70,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
|
||||||
internal async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
internal async Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
|
@ -12,11 +12,11 @@ using FrostFS.Session;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider
|
internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider
|
||||||
{
|
{
|
||||||
readonly SessionProvider sessions = new(context);
|
readonly SessionProvider sessions = new(envCtx);
|
||||||
|
|
||||||
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
|
||||||
{
|
{
|
||||||
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
internal async IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args)
|
internal async IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.OwnerId ??= Context.Owner;
|
ctx.OwnerId ??= EnvironmentContext.Owner;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -147,7 +147,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx)
|
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -172,7 +172,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
|
||||||
Removed
|
Removed
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx)
|
private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx)
|
||||||
{
|
{
|
||||||
var request = GetContainerRequest(id, null, ctx);
|
var request = GetContainerRequest(id, null, ctx);
|
||||||
|
|
||||||
|
|
|
@ -12,27 +12,33 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
|
private readonly NetmapService.NetmapServiceClient netmapServiceClient;
|
||||||
|
|
||||||
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context)
|
internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
this.netmapServiceClient = netmapServiceClient;
|
this.netmapServiceClient = netmapServiceClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<NetworkSettings> GetNetworkSettingsAsync(Context ctx)
|
internal async Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
|
||||||
{
|
{
|
||||||
if (Context.NetworkSettings != null)
|
if (EnvironmentContext.NetworkSettings != null)
|
||||||
return Context.NetworkSettings;
|
return EnvironmentContext.NetworkSettings;
|
||||||
|
|
||||||
var info = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
|
var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
var settings = new NetworkSettings();
|
var settings = new NetworkSettings();
|
||||||
|
|
||||||
foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters)
|
var info = response.Body.NetworkInfo;
|
||||||
|
|
||||||
|
settings.Epoch = info.CurrentEpoch;
|
||||||
|
settings.MagicNumber = info.MagicNumber;
|
||||||
|
settings.MsPerBlock = info.MsPerBlock;
|
||||||
|
|
||||||
|
foreach (var param in info.NetworkConfig.Parameters)
|
||||||
{
|
{
|
||||||
SetNetworksParam(param, settings);
|
SetNetworksParam(param, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.NetworkSettings = settings;
|
EnvironmentContext.NetworkSettings = settings;
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +46,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
internal async Task<FrostFsNodeInfo> GetLocalNodeInfoAsync(PrmNodeInfo args)
|
internal async Task<FrostFsNodeInfo> GetLocalNodeInfoAsync(PrmNodeInfo args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -53,6 +59,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
request.AddMetaHeader(args.XHeaders);
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(ctx.Key);
|
request.Sign(ctx.Key);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
@ -60,9 +68,9 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
return response.Body.ToModel();
|
return response.Body.ToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(Context ctx)
|
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(CallContext ctx)
|
||||||
{
|
{
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -83,7 +91,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor
|
||||||
internal async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
|
internal async Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
|
|
@ -20,14 +20,14 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
private readonly SessionProvider sessions;
|
private readonly SessionProvider sessions;
|
||||||
private ObjectService.ObjectServiceClient client;
|
private ObjectService.ObjectServiceClient client;
|
||||||
|
|
||||||
internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env)
|
internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env)
|
||||||
: base(env)
|
: base(env)
|
||||||
{
|
{
|
||||||
this.sessions = new(Context);
|
this.sessions = new(EnvironmentContext);
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
public async ValueTask<SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
|
||||||
{
|
{
|
||||||
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
internal async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
internal async Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -74,7 +74,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -108,7 +108,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
internal async Task DeleteObjectAsync(PrmObjectDelete args)
|
internal async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
ctx.Key ??= Context.Key?.ECDsaKey;
|
ctx.Key ??= EnvironmentContext.Key?.ECDsaKey;
|
||||||
|
|
||||||
if (ctx.Key == null)
|
if (ctx.Key == null)
|
||||||
throw new InvalidObjectException(nameof(ctx.Key));
|
throw new InvalidObjectException(nameof(ctx.Key));
|
||||||
|
@ -238,7 +238,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||||
var token = new FrostFsSessionToken(tokenRaw.Serialize());
|
var token = new FrostFsSessionToken(tokenRaw.Serialize(), tokenRaw.Body.Id.ToUuid());
|
||||||
|
|
||||||
args.SessionToken = token;
|
args.SessionToken = token;
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
|
|
||||||
if (args.MaxObjectSizeCache == 0)
|
if (args.MaxObjectSizeCache == 0)
|
||||||
{
|
{
|
||||||
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
|
var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx })
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
|
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
|
||||||
|
@ -352,7 +352,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chunkBuffer = Context.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
|
chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
|
||||||
isRentBuffer = true;
|
isRentBuffer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +404,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ObjectStreamer> GetUploadStream(PrmObjectPut args, Context ctx)
|
private async Task<ObjectStreamer> GetUploadStream(PrmObjectPut args, CallContext ctx)
|
||||||
{
|
{
|
||||||
var header = args.Header!;
|
var header = args.Header!;
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
return await PutObjectInit(initRequest, ctx).ConfigureAwait(false);
|
return await PutObjectInit(initRequest, ctx).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<FrostFsObject> GetObject(GetRequest request, Context ctx)
|
private async Task<FrostFsObject> GetObject(GetRequest request, CallContext ctx)
|
||||||
{
|
{
|
||||||
var reader = GetObjectInit(request, ctx);
|
var reader = GetObjectInit(request, ctx);
|
||||||
|
|
||||||
|
@ -461,7 +461,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
return modelObject;
|
return modelObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx)
|
private ObjectReader GetObjectInit(GetRequest initRequest, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (initRequest is null)
|
if (initRequest is null)
|
||||||
throw new ArgumentNullException(nameof(initRequest));
|
throw new ArgumentNullException(nameof(initRequest));
|
||||||
|
@ -471,7 +471,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
return new ObjectReader(call);
|
return new ObjectReader(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ObjectStreamer> PutObjectInit(PutRequest initRequest, Context ctx)
|
private async Task<ObjectStreamer> PutObjectInit(PutRequest initRequest, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (initRequest is null)
|
if (initRequest is null)
|
||||||
{
|
{
|
||||||
|
@ -485,7 +485,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
return new ObjectStreamer(call);
|
return new ObjectStreamer(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<ObjectID> SearchObjects(SearchRequest request, Context ctx)
|
private async IAsyncEnumerable<ObjectID> SearchObjects(SearchRequest request, CallContext ctx)
|
||||||
{
|
{
|
||||||
using var stream = GetSearchReader(request, ctx);
|
using var stream = GetSearchReader(request, ctx);
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx)
|
private SearchReader GetSearchReader(SearchRequest initRequest, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (initRequest is null)
|
if (initRequest is null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
|
private readonly SessionService.SessionServiceClient? _sessionServiceClient;
|
||||||
|
|
||||||
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context)
|
internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_sessionServiceClient = sessionServiceClient;
|
_sessionServiceClient = sessionServiceClient;
|
||||||
|
@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
|
||||||
{
|
{
|
||||||
var ctx = args.Context!;
|
var ctx = args.Context!;
|
||||||
|
|
||||||
ctx.OwnerId ??= Context.Owner;
|
ctx.OwnerId ??= EnvironmentContext.Owner;
|
||||||
|
|
||||||
var request = new CreateRequest
|
var request = new CreateRequest
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@ internal sealed class SessionServiceProvider : ContextAccessor
|
||||||
return await CreateSession(request, args.Context!).ConfigureAwait(false);
|
return await CreateSession(request, args.Context!).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<SessionToken> CreateSession(CreateRequest request, Context ctx)
|
internal async Task<SessionToken> CreateSession(CreateRequest request, CallContext ctx)
|
||||||
{
|
{
|
||||||
var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal class ContextAccessor(ClientEnvironment context)
|
internal class ContextAccessor(EnvironmentContext context)
|
||||||
{
|
{
|
||||||
protected ClientEnvironment Context { get; set; } = context;
|
protected EnvironmentContext EnvironmentContext { get; set; } = context;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,17 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal interface ISessionProvider
|
internal interface ISessionProvider
|
||||||
{
|
{
|
||||||
ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx);
|
ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class SessionProvider(ClientEnvironment env)
|
internal sealed class SessionProvider(EnvironmentContext envCtx)
|
||||||
{
|
{
|
||||||
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
// TODO: implement cache for session in the next iteration
|
||||||
|
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (args.SessionToken is null)
|
if (args.SessionToken is null)
|
||||||
{
|
{
|
||||||
return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
|
return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx })
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ using Grpc.Net.Client;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class ClientEnvironment(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
|
public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable
|
||||||
{
|
{
|
||||||
private ArrayPool<byte>? _arrayPool;
|
private ArrayPool<byte>? _arrayPool;
|
||||||
|
|
|
@ -4,6 +4,10 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public class NetworkSettings
|
public class NetworkSettings
|
||||||
{
|
{
|
||||||
|
public ulong Epoch { get; internal set; }
|
||||||
|
public ulong MagicNumber { get; internal set; }
|
||||||
|
public long MsPerBlock { get; internal set; }
|
||||||
|
|
||||||
public ulong AuditFee { get; internal set; }
|
public ulong AuditFee { get; internal set; }
|
||||||
public ulong BasicIncomeRate { get; internal set; }
|
public ulong BasicIncomeRate { get; internal set; }
|
||||||
public ulong ContainerFee { get; internal set; }
|
public ulong ContainerFee { get; internal set; }
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal static class ObjectTools
|
internal static class ObjectTools
|
||||||
{
|
{
|
||||||
internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx)
|
internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
|
||||||
{
|
{
|
||||||
var grpcHeader = CreateHeader(header, [], ctx);
|
var grpcHeader = CreateHeader(header, [], ctx);
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ internal static class ObjectTools
|
||||||
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
|
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Object.Object CreateObject(FrostFsObject @object, Context ctx)
|
internal static Object.Object CreateObject(FrostFsObject @object, CallContext ctx)
|
||||||
{
|
{
|
||||||
@object.Header.OwnerId ??= ctx.OwnerId;
|
@object.Header.OwnerId ??= ctx.OwnerId;
|
||||||
@object.Header.Version ??= ctx.Version;
|
@object.Header.Version ??= ctx.Version;
|
||||||
|
@ -53,7 +53,7 @@ internal static class ObjectTools
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, Context ctx)
|
internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, CallContext ctx)
|
||||||
{
|
{
|
||||||
if (split == null)
|
if (split == null)
|
||||||
return;
|
return;
|
||||||
|
@ -85,7 +85,7 @@ internal static class ObjectTools
|
||||||
grpcHeader.Split.Previous = split.Previous?.ToMessage();
|
grpcHeader.Split.Previous = split.Previous?.ToMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, Context ctx)
|
internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, CallContext ctx)
|
||||||
{
|
{
|
||||||
header.OwnerId ??= ctx.OwnerId;
|
header.OwnerId ??= ctx.OwnerId;
|
||||||
header.Version ??= ctx.Version;
|
header.Version ??= ctx.Version;
|
||||||
|
|
|
@ -76,6 +76,11 @@ public static class RequestSigner
|
||||||
|
|
||||||
public static byte[] SignData(this ECDsa key, byte[] data)
|
public static byte[] SignData(this ECDsa key, byte[] data)
|
||||||
{
|
{
|
||||||
|
if (key is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
var hash = new byte[65];
|
var hash = new byte[65];
|
||||||
hash[0] = 0x04;
|
hash[0] = 0x04;
|
||||||
|
|
||||||
|
|
62
src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs
Normal file
62
src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
using FrostFS.SDK.ProtosV2.Interfaces;
|
||||||
|
using FrostFS.Session;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.Accounting;
|
||||||
|
|
||||||
|
public partial class BalanceRequest : IRequest
|
||||||
|
{
|
||||||
|
IMetaHeader IVerifiableMessage.GetMetaHeader()
|
||||||
|
{
|
||||||
|
return MetaHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
IVerificationHeader IVerifiableMessage.GetVerificationHeader()
|
||||||
|
{
|
||||||
|
return VerifyHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader)
|
||||||
|
{
|
||||||
|
MetaHeader = (RequestMetaHeader)metaHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader)
|
||||||
|
{
|
||||||
|
VerifyHeader = (RequestVerificationHeader)verificationHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMessage GetBody()
|
||||||
|
{
|
||||||
|
return Body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class BalanceResponse : IResponse
|
||||||
|
{
|
||||||
|
IMetaHeader IVerifiableMessage.GetMetaHeader()
|
||||||
|
{
|
||||||
|
return MetaHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
IVerificationHeader IVerifiableMessage.GetVerificationHeader()
|
||||||
|
{
|
||||||
|
return VerifyHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader)
|
||||||
|
{
|
||||||
|
MetaHeader = (ResponseMetaHeader)metaHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader)
|
||||||
|
{
|
||||||
|
VerifyHeader = (ResponseVerificationHeader)verificationHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMessage GetBody()
|
||||||
|
{
|
||||||
|
return Body;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +1,14 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public abstract class ContainerTestsBase
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
{
|
|
||||||
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
|
||||||
|
|
||||||
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
|
||||||
protected ContainerMocker Mocker { get; set; }
|
|
||||||
|
|
||||||
protected ContainerTestsBase()
|
|
||||||
{
|
|
||||||
Settings = Options.Create(new SingleOwnerClientSettings
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
Host = "http://localhost:8080"
|
|
||||||
});
|
|
||||||
|
|
||||||
Mocker = new ContainerMocker(this.key)
|
|
||||||
{
|
|
||||||
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
|
||||||
Version = new FrostFsVersion(2, 13),
|
|
||||||
ContainerGuid = Guid.NewGuid()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IFrostFSClient GetClient()
|
|
||||||
{
|
|
||||||
return ClientV2.FrostFSClient.GetTestInstance(
|
|
||||||
Settings,
|
|
||||||
null,
|
|
||||||
new NetworkMocker(this.key).GetMock().Object,
|
|
||||||
new SessionMocker(this.key).GetMock().Object,
|
|
||||||
Mocker.GetMock().Object,
|
|
||||||
new ObjectMocker(this.key).GetMock().Object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContainerTest : ContainerTestsBase
|
public class ContainerTest : ContainerTestsBase
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
40
src/FrostFS.SDK.Tests/ContainerTestsBase.cs
Normal file
40
src/FrostFS.SDK.Tests/ContainerTestsBase.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
public abstract class ContainerTestsBase
|
||||||
|
{
|
||||||
|
internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
|
||||||
|
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
||||||
|
protected ContainerMocker Mocker { get; set; }
|
||||||
|
|
||||||
|
protected ContainerTestsBase()
|
||||||
|
{
|
||||||
|
Settings = Options.Create(new SingleOwnerClientSettings
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Host = "http://localhost:8080"
|
||||||
|
});
|
||||||
|
|
||||||
|
Mocker = new ContainerMocker(this.key)
|
||||||
|
{
|
||||||
|
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
||||||
|
Version = new FrostFsVersion(2, 13),
|
||||||
|
ContainerGuid = Guid.NewGuid()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IFrostFSClient GetClient()
|
||||||
|
{
|
||||||
|
return ClientV2.FrostFSClient.GetTestInstance(
|
||||||
|
Settings,
|
||||||
|
null,
|
||||||
|
new NetworkMocker(this.key).GetMock().Object,
|
||||||
|
new SessionMocker(this.key).GetMock().Object,
|
||||||
|
Mocker.GetMock().Object,
|
||||||
|
new ObjectMocker(this.key).GetMock().Object);
|
||||||
|
}
|
||||||
|
}
|
6
src/FrostFS.SDK.Tests/GlobalSuppressions.cs
Normal file
6
src/FrostFS.SDK.Tests/GlobalSuppressions.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
|
// attributes that are applied to this project.
|
||||||
|
// Project-level suppressions either have no target or are given
|
||||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,9 @@ public class MetricsInterceptor() : Interceptor
|
||||||
ClientInterceptorContext<TRequest, TResponse> context,
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
{
|
{
|
||||||
var call = continuation(request, context);
|
ArgumentNullException.ThrowIfNull(continuation);
|
||||||
|
|
||||||
|
using var call = continuation(request, context);
|
||||||
|
|
||||||
return new AsyncUnaryCall<TResponse>(
|
return new AsyncUnaryCall<TResponse>(
|
||||||
HandleUnaryResponse(call),
|
HandleUnaryResponse(call),
|
||||||
|
@ -27,7 +29,7 @@ public class MetricsInterceptor() : Interceptor
|
||||||
var watch = new Stopwatch();
|
var watch = new Stopwatch();
|
||||||
watch.Start();
|
watch.Start();
|
||||||
|
|
||||||
var response = await call.ResponseAsync;
|
var response = await call.ResponseAsync.ConfigureAwait(false);
|
||||||
|
|
||||||
watch.Stop();
|
watch.Stop();
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,11 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader)
|
||||||
OwnerId = objectHeader.OwnerId!.ToMessage()
|
OwnerId = objectHeader.OwnerId!.ToMessage()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var attr in objectHeader.Attributes)
|
if (objectHeader.Attributes != null)
|
||||||
header.Attributes.Add(attr.ToMessage());
|
{
|
||||||
|
foreach (var attr in objectHeader.Attributes)
|
||||||
|
header.Attributes.Add(attr.ToMessage());
|
||||||
|
}
|
||||||
|
|
||||||
var response = new GetResponse
|
var response = new GetResponse
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
using FrostFS.SDK.ProtosV2.Interfaces;
|
using FrostFS.SDK.ProtosV2.Interfaces;
|
||||||
|
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
@ -6,13 +8,15 @@ namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public class ClientStreamWriter : IClientStreamWriter<IRequest>
|
public class ClientStreamWriter : IClientStreamWriter<IRequest>
|
||||||
{
|
{
|
||||||
public List<IRequest> Messages { get; set; } = [];
|
private WriteOptions? _options;
|
||||||
|
|
||||||
|
public Collection<IRequest> Messages { get; } = [];
|
||||||
public bool CompletedTask { get; private set; }
|
public bool CompletedTask { get; private set; }
|
||||||
|
|
||||||
public WriteOptions? WriteOptions
|
public WriteOptions? WriteOptions
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => _options;
|
||||||
set => throw new NotImplementedException();
|
set => _options = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CompleteAsync()
|
public Task CompleteAsync()
|
||||||
|
|
|
@ -25,7 +25,10 @@ public abstract class ServiceBase(string key)
|
||||||
public static FrostFsVersion DefaultVersion { get; } = new(2, 13);
|
public static FrostFsVersion DefaultVersion { get; } = new(2, 13);
|
||||||
public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1));
|
public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1));
|
||||||
|
|
||||||
public Metadata Metadata { get; protected set; }
|
#pragma warning disable CA2227 // this is specific object, should be treated as is
|
||||||
|
public Metadata? Metadata { get; set; }
|
||||||
|
#pragma warning restore CA2227
|
||||||
|
|
||||||
public DateTime? DateTime { get; protected set; }
|
public DateTime? DateTime { get; protected set; }
|
||||||
|
|
||||||
public CancellationToken CancellationToken { get; protected set; }
|
public CancellationToken CancellationToken { get; protected set; }
|
||||||
|
@ -35,6 +38,8 @@ public abstract class ServiceBase(string key)
|
||||||
|
|
||||||
protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response)
|
protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(response);
|
||||||
|
|
||||||
var verifyHeader = new ResponseVerificationHeader
|
var verifyHeader = new ResponseVerificationHeader
|
||||||
{
|
{
|
||||||
MetaSignature = new Refs.Signature
|
MetaSignature = new Refs.Signature
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
using FrostFS.Container;
|
using FrostFS.Container;
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
|
@ -41,7 +43,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
|
||||||
putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse);
|
putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse);
|
||||||
|
|
||||||
var metadata = new Metadata();
|
var metadata = new Metadata();
|
||||||
var putContainerResponse = new AsyncUnaryCall<PutResponse>(
|
using var putContainerResponse = new AsyncUnaryCall<PutResponse>(
|
||||||
Task.FromResult(putResponse),
|
Task.FromResult(putResponse),
|
||||||
Task.FromResult(metadata),
|
Task.FromResult(metadata),
|
||||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||||
|
@ -180,7 +182,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
|
||||||
|
|
||||||
public bool ReturnContainerRemoved { get; set; }
|
public bool ReturnContainerRemoved { get; set; }
|
||||||
|
|
||||||
public List<byte[]> ContainerIds { get; set; } = [];
|
public Collection<byte[]> ContainerIds { get; } = [];
|
||||||
|
|
||||||
public List<RequestData<DeleteRequest>> Requests { get; set; } = [];
|
public Collection<RequestData<DeleteRequest>> Requests { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,18 +25,17 @@ public class NetworkMocker(string key) : ServiceBase(key)
|
||||||
"MaintenanceModeAllowed"
|
"MaintenanceModeAllowed"
|
||||||
];
|
];
|
||||||
|
|
||||||
public Dictionary<string, byte[]>? Parameters { get; set; }
|
public Dictionary<string, byte[]> Parameters { get; } = [];
|
||||||
|
|
||||||
public LocalNodeInfoResponse NodeInfoResponse { get; set; }
|
public LocalNodeInfoResponse? NodeInfoResponse { get; set; }
|
||||||
|
|
||||||
public LocalNodeInfoRequest LocalNodeInfoRequest { get; set; }
|
public LocalNodeInfoRequest? LocalNodeInfoRequest { get; set; }
|
||||||
|
|
||||||
public NetworkInfoRequest NetworkInfoRequest { get; set; }
|
public NetworkInfoRequest? NetworkInfoRequest { get; set; }
|
||||||
|
|
||||||
public NetmapSnapshotResponse NetmapSnapshotResponse { get; set; }
|
public NetmapSnapshotResponse? NetmapSnapshotResponse { get; set; }
|
||||||
|
|
||||||
public NetmapSnapshotRequest NetmapSnapshotRequest { get; set; }
|
|
||||||
|
|
||||||
|
public NetmapSnapshotRequest? NetmapSnapshotRequest { get; set; }
|
||||||
|
|
||||||
public Mock<NetmapService.NetmapServiceClient> GetMock()
|
public Mock<NetmapService.NetmapServiceClient> GetMock()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
using FrostFS.Object;
|
using FrostFS.Object;
|
||||||
|
@ -92,7 +93,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ResultObjectIds != null)
|
if (ResultObjectIds != null && ResultObjectIds.Count > 0)
|
||||||
{
|
{
|
||||||
PutResponse putResponse = new()
|
PutResponse putResponse = new()
|
||||||
{
|
{
|
||||||
|
@ -197,14 +198,14 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
|
||||||
|
|
||||||
public Header? HeadResponse { get; set; }
|
public Header? HeadResponse { get; set; }
|
||||||
|
|
||||||
public List<byte[]>? ResultObjectIds { get; set; }
|
public Collection<byte[]>? ResultObjectIds { get; } = [];
|
||||||
|
|
||||||
public ClientStreamWriter? ClientStreamWriter { get; private set; } = new();
|
public ClientStreamWriter? ClientStreamWriter { get; private set; } = new();
|
||||||
|
|
||||||
public List<PutSingleRequest> PutSingleRequests { get; private set; } = [];
|
public Collection<PutSingleRequest> PutSingleRequests { get; private set; } = [];
|
||||||
|
|
||||||
public List<DeleteRequest> DeleteRequests { get; private set; } = [];
|
public Collection<DeleteRequest> DeleteRequests { get; private set; } = [];
|
||||||
|
|
||||||
public List<HeadRequest> HeadRequests { get; private set; } = [];
|
public Collection<HeadRequest> HeadRequests { get; private set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@ using Moq;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
|
||||||
public class SessionMocker(string key) : ServiceBase(key)
|
public class SessionMocker(string key) : ServiceBase(key)
|
||||||
{
|
{
|
||||||
public byte[]? SessionId { get; set; }
|
public byte[]? SessionId { get; set; }
|
||||||
|
|
||||||
public byte[]? SessionKey { get; set; }
|
public byte[]? SessionKey { get; set; }
|
||||||
|
|
||||||
public CreateRequest CreateSessionRequest { get; private set; }
|
public CreateRequest? CreateSessionRequest { get; private set; }
|
||||||
|
|
||||||
public Mock<SessionService.SessionServiceClient> GetMock()
|
public Mock<SessionService.SessionServiceClient> GetMock()
|
||||||
{
|
{
|
||||||
|
@ -24,7 +25,7 @@ public class SessionMocker(string key) : ServiceBase(key)
|
||||||
|
|
||||||
if (SessionId == null)
|
if (SessionId == null)
|
||||||
{
|
{
|
||||||
SessionId = new byte[32];
|
SessionId = new byte[16];
|
||||||
rand.NextBytes(SessionId);
|
rand.NextBytes(SessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,13 @@
|
||||||
using System.Security.Cryptography;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using FrostFS.Netmap;
|
using FrostFS.Netmap;
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
|
||||||
using FrostFS.SDK.ClientV2;
|
|
||||||
using FrostFS.SDK.Cryptography;
|
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public abstract class NetworkTestsBase
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
{
|
|
||||||
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
|
||||||
|
|
||||||
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
|
||||||
|
|
||||||
protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13);
|
|
||||||
|
|
||||||
protected ECDsa ECDsaKey { get; set; }
|
|
||||||
protected FrostFsOwner OwnerId { get; set; }
|
|
||||||
protected NetworkMocker Mocker { get; set; }
|
|
||||||
|
|
||||||
protected NetworkTestsBase()
|
|
||||||
{
|
|
||||||
Settings = Options.Create(new SingleOwnerClientSettings
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
Host = "http://localhost:8080"
|
|
||||||
});
|
|
||||||
|
|
||||||
ECDsaKey = key.LoadWif();
|
|
||||||
OwnerId = FrostFsOwner.FromKey(ECDsaKey);
|
|
||||||
|
|
||||||
Mocker = new NetworkMocker(this.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IFrostFSClient GetClient()
|
|
||||||
{
|
|
||||||
return ClientV2.FrostFSClient.GetTestInstance(
|
|
||||||
Settings,
|
|
||||||
null,
|
|
||||||
Mocker.GetMock().Object,
|
|
||||||
new SessionMocker(this.key).GetMock().Object,
|
|
||||||
new ContainerMocker(this.key).GetMock().Object,
|
|
||||||
new ObjectMocker(this.key).GetMock().Object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NetworkTest : NetworkTestsBase
|
public class NetworkTest : NetworkTestsBase
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -57,27 +15,24 @@ public class NetworkTest : NetworkTestsBase
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public async void NetworkSettingsTest(bool useContext)
|
public async void NetworkSettingsTest(bool useContext)
|
||||||
{
|
{
|
||||||
Mocker.Parameters = new Dictionary<string, byte[]>
|
Mocker.Parameters.Add("AuditFee", [1]);
|
||||||
{
|
Mocker.Parameters.Add("BasicIncomeRate", [2]);
|
||||||
{ "AuditFee", [1] },
|
Mocker.Parameters.Add("ContainerFee", [3]);
|
||||||
{ "BasicIncomeRate", [2] },
|
Mocker.Parameters.Add("ContainerAliasFee", [4]);
|
||||||
{ "ContainerFee", [3] },
|
Mocker.Parameters.Add("EpochDuration", [5]);
|
||||||
{ "ContainerAliasFee", [4] },
|
Mocker.Parameters.Add("InnerRingCandidateFee", [6]);
|
||||||
{ "EpochDuration", [5] },
|
Mocker.Parameters.Add("MaxECDataCount", [7]);
|
||||||
{ "InnerRingCandidateFee", [6] },
|
Mocker.Parameters.Add("MaxECParityCount", [8]);
|
||||||
{ "MaxECDataCount", [7] },
|
Mocker.Parameters.Add("MaxObjectSize", [9]);
|
||||||
{ "MaxECParityCount", [8] },
|
Mocker.Parameters.Add("WithdrawFee", [10]);
|
||||||
{ "MaxObjectSize", [9] },
|
Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]);
|
||||||
{ "WithdrawFee", [10] },
|
Mocker.Parameters.Add("MaintenanceModeAllowed", [1]);
|
||||||
{ "HomomorphicHashingDisabled", [1] },
|
|
||||||
{ "MaintenanceModeAllowed", [1] },
|
|
||||||
};
|
|
||||||
|
|
||||||
var param = new PrmNetworkSettings();
|
var param = new PrmNetworkSettings();
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.Context = new Context
|
param.Context = new CallContext
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -119,6 +74,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Assert.NotNull(Mocker.NetworkInfoRequest);
|
||||||
Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders);
|
Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders);
|
||||||
Assert.Null(Mocker.DateTime);
|
Assert.Null(Mocker.DateTime);
|
||||||
}
|
}
|
||||||
|
@ -127,6 +83,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
|
|
||||||
public async void NetmapSnapshotTest(bool useContext)
|
public async void NetmapSnapshotTest(bool useContext)
|
||||||
{
|
{
|
||||||
var body = new NetmapSnapshotResponse.Types.Body
|
var body = new NetmapSnapshotResponse.Types.Body
|
||||||
|
@ -164,7 +121,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
param.Context = new Context
|
param.Context = new CallContext
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -210,6 +167,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
|
Assert.NotNull(Mocker.NetmapSnapshotRequest);
|
||||||
Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
|
Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
|
||||||
Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key);
|
Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key);
|
||||||
Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value);
|
Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value);
|
||||||
|
@ -222,6 +180,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Assert.NotNull(Mocker.NetmapSnapshotRequest);
|
||||||
Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
|
Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
|
||||||
Assert.Null(Mocker.DateTime);
|
Assert.Null(Mocker.DateTime);
|
||||||
}
|
}
|
||||||
|
@ -254,7 +213,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
param.Context = new Context
|
param.Context = new CallContext
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -282,6 +241,7 @@ public class NetworkTest : NetworkTestsBase
|
||||||
Assert.Equal("value1", result.Attributes["key1"]);
|
Assert.Equal("value1", result.Attributes["key1"]);
|
||||||
Assert.Equal("value2", result.Attributes["key2"]);
|
Assert.Equal("value2", result.Attributes["key2"]);
|
||||||
|
|
||||||
|
Assert.NotNull(Mocker.LocalNodeInfoRequest);
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders);
|
Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders);
|
||||||
|
|
48
src/FrostFS.SDK.Tests/NetworkTestsBase.cs
Normal file
48
src/FrostFS.SDK.Tests/NetworkTestsBase.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
|
public abstract class NetworkTestsBase
|
||||||
|
{
|
||||||
|
internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
|
||||||
|
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
||||||
|
|
||||||
|
protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13);
|
||||||
|
|
||||||
|
protected ECDsa ECDsaKey { get; set; }
|
||||||
|
protected FrostFsOwner OwnerId { get; set; }
|
||||||
|
protected NetworkMocker Mocker { get; set; }
|
||||||
|
|
||||||
|
protected NetworkTestsBase()
|
||||||
|
{
|
||||||
|
Settings = Options.Create(new SingleOwnerClientSettings
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Host = "http://localhost:8080"
|
||||||
|
});
|
||||||
|
|
||||||
|
ECDsaKey = key.LoadWif();
|
||||||
|
OwnerId = FrostFsOwner.FromKey(ECDsaKey);
|
||||||
|
|
||||||
|
Mocker = new NetworkMocker(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IFrostFSClient GetClient()
|
||||||
|
{
|
||||||
|
return ClientV2.FrostFSClient.GetTestInstance(
|
||||||
|
Settings,
|
||||||
|
null,
|
||||||
|
Mocker.GetMock().Object,
|
||||||
|
new SessionMocker(this.key).GetMock().Object,
|
||||||
|
new ContainerMocker(this.key).GetMock().Object,
|
||||||
|
new ObjectMocker(this.key).GetMock().Object);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,71 +1,19 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ClientV2;
|
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public abstract class ObjectTestsBase
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
{
|
[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
|
||||||
protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
|
||||||
|
|
||||||
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
|
||||||
protected FrostFsContainerId ContainerId { get; set; }
|
|
||||||
|
|
||||||
protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key);
|
|
||||||
protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key);
|
|
||||||
protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key);
|
|
||||||
protected ObjectMocker Mocker { get; set; }
|
|
||||||
|
|
||||||
protected ObjectTestsBase()
|
|
||||||
{
|
|
||||||
var ecdsaKey = key.LoadWif();
|
|
||||||
|
|
||||||
Settings = Options.Create(new SingleOwnerClientSettings
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
Host = "http://localhost:8080"
|
|
||||||
});
|
|
||||||
|
|
||||||
Mocker = new ObjectMocker(key)
|
|
||||||
{
|
|
||||||
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
|
||||||
Version = new FrostFsVersion(2, 13),
|
|
||||||
ContainerGuid = Guid.NewGuid()
|
|
||||||
};
|
|
||||||
|
|
||||||
ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
|
|
||||||
|
|
||||||
Mocker.ObjectHeader = new(
|
|
||||||
ContainerId,
|
|
||||||
FrostFsObjectType.Regular,
|
|
||||||
[new FrostFsAttributePair("k", "v")],
|
|
||||||
null,
|
|
||||||
FrostFsOwner.FromKey(ecdsaKey),
|
|
||||||
new FrostFsVersion(2, 13));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IFrostFSClient GetClient()
|
|
||||||
{
|
|
||||||
return FrostFSClient.GetTestInstance(
|
|
||||||
Settings,
|
|
||||||
null,
|
|
||||||
NetworkMocker.GetMock().Object,
|
|
||||||
SessionMocker.GetMock().Object,
|
|
||||||
ContainerMocker.GetMock().Object,
|
|
||||||
Mocker.GetMock().Object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ObjectTest : ObjectTestsBase
|
public class ObjectTest : ObjectTestsBase
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -75,7 +23,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
|
|
||||||
var ecdsaKey = key.LoadWif();
|
var ecdsaKey = key.LoadWif();
|
||||||
|
|
||||||
var ctx = new Context
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Key = ecdsaKey,
|
Key = ecdsaKey,
|
||||||
OwnerId = FrostFsOwner.FromKey(ecdsaKey),
|
OwnerId = FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
@ -88,18 +36,21 @@ public class ObjectTest : ObjectTestsBase
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
||||||
Assert.Equal(Mocker.ObjectHeader!.ContainerId.GetValue(), result.Header.ContainerId.GetValue());
|
Assert.NotNull(Mocker.ObjectHeader);
|
||||||
Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value);
|
|
||||||
|
Assert.Equal(Mocker.ObjectHeader.ContainerId.GetValue(), result.Header.ContainerId.GetValue());
|
||||||
|
Assert.Equal(Mocker.ObjectHeader.OwnerId!.Value, result.Header.OwnerId!.Value);
|
||||||
Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength);
|
Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength);
|
||||||
|
Assert.NotNull(result.Header.Attributes);
|
||||||
Assert.Single(result.Header.Attributes);
|
Assert.Single(result.Header.Attributes);
|
||||||
Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key);
|
Assert.Equal(Mocker.ObjectHeader.Attributes![0].Key, result.Header.Attributes[0].Key);
|
||||||
Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value, result.Header.Attributes[0].Value);
|
Assert.Equal(Mocker.ObjectHeader.Attributes![0].Value, result.Header.Attributes[0].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void PutObjectTest()
|
public async void PutObjectTest()
|
||||||
{
|
{
|
||||||
Mocker.ResultObjectIds = new([SHA256.HashData([])]);
|
Mocker.ResultObjectIds.Add(SHA256.HashData([]));
|
||||||
|
|
||||||
Random rnd = new();
|
Random rnd = new();
|
||||||
var bytes = new byte[1024];
|
var bytes = new byte[1024];
|
||||||
|
@ -134,7 +85,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ClientCutTest()
|
public async void ClientCutTest()
|
||||||
{
|
{
|
||||||
NetworkMocker.Parameters = new Dictionary<string, byte[]>() { { "MaxObjectSize", [0x0, 0xa] } };
|
NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]);
|
||||||
|
|
||||||
var blockSize = 2560;
|
var blockSize = 2560;
|
||||||
byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg");
|
byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg");
|
||||||
|
@ -150,17 +101,19 @@ public class ObjectTest : ObjectTestsBase
|
||||||
|
|
||||||
Random rnd = new();
|
Random rnd = new();
|
||||||
|
|
||||||
List<byte[]> objIds = new([new byte[32], new byte[32], new byte[32]]);
|
Collection<byte[]> objIds = new([new byte[32], new byte[32], new byte[32]]);
|
||||||
rnd.NextBytes(objIds.ElementAt(0));
|
rnd.NextBytes(objIds.ElementAt(0));
|
||||||
rnd.NextBytes(objIds.ElementAt(1));
|
rnd.NextBytes(objIds.ElementAt(1));
|
||||||
rnd.NextBytes(objIds.ElementAt(2));
|
rnd.NextBytes(objIds.ElementAt(2));
|
||||||
|
|
||||||
Mocker.ResultObjectIds = objIds;
|
foreach (var objId in objIds)
|
||||||
|
Mocker.ResultObjectIds.Add(objId);
|
||||||
|
|
||||||
var result = await GetClient().PutObjectAsync(param);
|
var result = await GetClient().PutObjectAsync(param);
|
||||||
|
|
||||||
var singleObjects = Mocker.PutSingleRequests.ToArray();
|
var singleObjects = Mocker.PutSingleRequests.ToArray();
|
||||||
|
|
||||||
|
Assert.NotNull(Mocker.ClientStreamWriter?.Messages);
|
||||||
var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray();
|
var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray();
|
||||||
|
|
||||||
Assert.Single(singleObjects);
|
Assert.Single(singleObjects);
|
||||||
|
@ -262,6 +215,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
|
|
||||||
Assert.Equal(FrostFsObjectType.Regular, response.ObjectType);
|
Assert.Equal(FrostFsObjectType.Regular, response.ObjectType);
|
||||||
|
|
||||||
|
Assert.NotNull(response.Attributes);
|
||||||
Assert.Single(response.Attributes);
|
Assert.Single(response.Attributes);
|
||||||
|
|
||||||
Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key);
|
Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key);
|
||||||
|
|
59
src/FrostFS.SDK.Tests/ObjectTestsBase.cs
Normal file
59
src/FrostFS.SDK.Tests/ObjectTestsBase.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using FrostFS.SDK.ClientV2;
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
public abstract class ObjectTestsBase
|
||||||
|
{
|
||||||
|
protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
|
||||||
|
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
||||||
|
protected FrostFsContainerId ContainerId { get; set; }
|
||||||
|
|
||||||
|
protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key);
|
||||||
|
protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key);
|
||||||
|
protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key);
|
||||||
|
protected ObjectMocker Mocker { get; set; }
|
||||||
|
|
||||||
|
protected ObjectTestsBase()
|
||||||
|
{
|
||||||
|
var ecdsaKey = key.LoadWif();
|
||||||
|
|
||||||
|
Settings = Options.Create(new SingleOwnerClientSettings
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Host = "http://localhost:8080"
|
||||||
|
});
|
||||||
|
|
||||||
|
Mocker = new ObjectMocker(key)
|
||||||
|
{
|
||||||
|
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
||||||
|
Version = new FrostFsVersion(2, 13),
|
||||||
|
ContainerGuid = Guid.NewGuid()
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
|
||||||
|
|
||||||
|
Mocker.ObjectHeader = new(
|
||||||
|
ContainerId,
|
||||||
|
FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("k", "v")],
|
||||||
|
null,
|
||||||
|
FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
new FrostFsVersion(2, 13));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IFrostFSClient GetClient()
|
||||||
|
{
|
||||||
|
return FrostFSClient.GetTestInstance(
|
||||||
|
Settings,
|
||||||
|
null,
|
||||||
|
NetworkMocker.GetMock().Object,
|
||||||
|
SessionMocker.GetMock().Object,
|
||||||
|
ContainerMocker.GetMock().Object,
|
||||||
|
Mocker.GetMock().Object);
|
||||||
|
}
|
||||||
|
}
|
603
src/FrostFS.SDK.Tests/PoolSmokeTests.cs
Normal file
603
src/FrostFS.SDK.Tests/PoolSmokeTests.cs
Normal file
|
@ -0,0 +1,603 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.SDK.ClientV2;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
using static FrostFS.Session.SessionToken.Types.Body;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.SmokeTests;
|
||||||
|
|
||||||
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
|
[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
|
||||||
|
public class PoolSmokeTests : SmokeTestsBase
|
||||||
|
{
|
||||||
|
private static readonly PrmWait lightWait = new(100, 1);
|
||||||
|
|
||||||
|
private InitParameters GetDefaultParams()
|
||||||
|
{
|
||||||
|
return new InitParameters
|
||||||
|
{
|
||||||
|
Key = keyString.LoadWif(),
|
||||||
|
|
||||||
|
NodeParams = [new(1, this.url, 100.0f)],
|
||||||
|
DialOptions = [new()
|
||||||
|
{
|
||||||
|
Authority = "",
|
||||||
|
Block = false,
|
||||||
|
DisableHealthCheck = false,
|
||||||
|
DisableRetry = false,
|
||||||
|
ReturnLastError = true,
|
||||||
|
Timeout = 30_000_000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ClientBuilder = null,
|
||||||
|
GracefulCloseOnSwitchTimeout = 30_000_000,
|
||||||
|
Logger = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void NetworkMapTest()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext());
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
var result = await pool.GetNetmapSnapshotAsync(default);
|
||||||
|
|
||||||
|
Assert.True(result.Epoch > 0);
|
||||||
|
Assert.Single(result.NodeInfoCollection);
|
||||||
|
|
||||||
|
var item = result.NodeInfoCollection[0];
|
||||||
|
Assert.Equal(2, item.Version.Major);
|
||||||
|
Assert.Equal(13, item.Version.Minor);
|
||||||
|
Assert.Equal(NodeState.Online, item.State);
|
||||||
|
Assert.True(item.PublicKey.Length > 0);
|
||||||
|
Assert.Single(item.Addresses);
|
||||||
|
Assert.Equal(9, item.Attributes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void NodeInfoTest()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext());
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
var result = await pool.GetNodeInfoAsync();
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Version.Major);
|
||||||
|
Assert.Equal(13, result.Version.Minor);
|
||||||
|
Assert.Equal(NodeState.Online, result.State);
|
||||||
|
Assert.Equal(33, result.PublicKey.Length);
|
||||||
|
Assert.Single(result.Addresses);
|
||||||
|
Assert.Equal(9, result.Attributes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void NodeInfoStatisticsTest()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var callbackText = string.Empty;
|
||||||
|
|
||||||
|
var ctx = new CallContext
|
||||||
|
{
|
||||||
|
Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"
|
||||||
|
};
|
||||||
|
|
||||||
|
var error = await pool.Dial(ctx).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
|
var result = await client.GetNodeInfoAsync();
|
||||||
|
|
||||||
|
Assert.False(string.IsNullOrEmpty(callbackText));
|
||||||
|
Assert.Contains(" took ", callbackText, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void GetSessionTest()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
var prm = new PrmSessionCreate(100);
|
||||||
|
|
||||||
|
var token = await pool.CreateSessionAsync(prm).ConfigureAwait(true);
|
||||||
|
|
||||||
|
var session = new Session.SessionToken().Deserialize(token.Token);
|
||||||
|
|
||||||
|
var ownerHash = Base58.Decode(OwnerId!.Value);
|
||||||
|
|
||||||
|
Assert.NotNull(session);
|
||||||
|
Assert.Null(session.Body.Container);
|
||||||
|
Assert.Null(session.Body.Object);
|
||||||
|
Assert.Equal(16, session.Body.Id.Length);
|
||||||
|
Assert.Equal(100ul, session.Body.Lifetime.Exp);
|
||||||
|
Assert.Equal(ownerHash, session.Body.OwnerId.Value);
|
||||||
|
Assert.Equal(33, session.Body.SessionKey.Length);
|
||||||
|
Assert.Equal(ContextOneofCase.None, session.Body.ContextCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void CreateObjectWithSessionToken()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))));
|
||||||
|
|
||||||
|
createContainerParam.XHeaders.Add("key1", "value1");
|
||||||
|
|
||||||
|
var containerId = await pool.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(1024);
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false,
|
||||||
|
SessionToken = token
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await pool.PutObjectAsync(param).ConfigureAwait(true);
|
||||||
|
|
||||||
|
var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId));
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
ReadOnlyMemory<byte>? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk.Value.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void FilterTest()
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
|
||||||
|
{
|
||||||
|
WaitParams = lightWait
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerId = await pool.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var bytes = new byte[] { 1, 2, 3 };
|
||||||
|
|
||||||
|
var ParentHeader = new FrostFsObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: FrostFsObjectType.Regular)
|
||||||
|
{
|
||||||
|
PayloadLength = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("fileName", "test")],
|
||||||
|
new FrostFsSplit()),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await pool.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
||||||
|
|
||||||
|
var ecdsaKey = this.keyString.LoadWif();
|
||||||
|
|
||||||
|
var networkInfo = await pool.GetNetmapSnapshotAsync();
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey)));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3));
|
||||||
|
|
||||||
|
var checkSum = CheckSum.CreateCheckSum(bytes);
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByPhysicallyStored());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter)
|
||||||
|
{
|
||||||
|
var resultObjectsCount = 0;
|
||||||
|
|
||||||
|
PrmObjectSearch searchParam = new(containerId) { Filters = [filter] };
|
||||||
|
|
||||||
|
await foreach (var objId in pool.SearchObjectsAsync(searchParam))
|
||||||
|
{
|
||||||
|
resultObjectsCount++;
|
||||||
|
var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||||
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
|
public async void SimpleScenarioTest(int objectSize)
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
bool callbackInvoked = false;
|
||||||
|
var ctx = new CallContext
|
||||||
|
{
|
||||||
|
// Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
Callback = new((CallStatistics cs) =>
|
||||||
|
{
|
||||||
|
callbackInvoked = true;
|
||||||
|
Assert.True(cs.ElapsedMicroSeconds > 0);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]))
|
||||||
|
{
|
||||||
|
Context = ctx
|
||||||
|
};
|
||||||
|
|
||||||
|
var createdContainer = await pool.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx });
|
||||||
|
Assert.NotNull(container);
|
||||||
|
Assert.True(callbackInvoked);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: createdContainer,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false,
|
||||||
|
Context = new CallContext
|
||||||
|
{
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await pool.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
bool hasObject = false;
|
||||||
|
await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] }))
|
||||||
|
{
|
||||||
|
hasObject = true;
|
||||||
|
|
||||||
|
var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId));
|
||||||
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
|
Assert.Single(objHeader.Attributes!);
|
||||||
|
Assert.Equal("fileName", objHeader.Attributes!.First().Key);
|
||||||
|
Assert.Equal("test", objHeader.Attributes!.First().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(hasObject);
|
||||||
|
|
||||||
|
var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId));
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
ReadOnlyMemory<byte>? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk.Value.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
await foreach (var _ in pool.ListContainersAsync())
|
||||||
|
{
|
||||||
|
Assert.Fail("Containers exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||||
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
|
public async void SimpleScenarioWithSessionTest(int objectSize)
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
var ctx = new CallContext
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
};
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
|
||||||
|
{
|
||||||
|
Context = ctx
|
||||||
|
};
|
||||||
|
|
||||||
|
var container = await pool.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx });
|
||||||
|
Assert.NotNull(containerInfo);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: container,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false,
|
||||||
|
Context = new CallContext
|
||||||
|
{
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
},
|
||||||
|
SessionToken = token
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await pool.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
bool hasObject = false;
|
||||||
|
await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token }))
|
||||||
|
{
|
||||||
|
hasObject = true;
|
||||||
|
|
||||||
|
var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token });
|
||||||
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
|
Assert.Single(objHeader.Attributes);
|
||||||
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
|
Assert.Equal("test", objHeader.Attributes.First().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(hasObject);
|
||||||
|
|
||||||
|
var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token });
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
ReadOnlyMemory<byte>? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk.Value.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
await foreach (var _ in pool.ListContainersAsync())
|
||||||
|
{
|
||||||
|
Assert.Fail("Containers exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB
|
||||||
|
[InlineData(64 * 1024 * 1024 - 1)]
|
||||||
|
[InlineData(64 * 1024 * 1024 + 1)]
|
||||||
|
[InlineData(2 * 64 * 1024 * 1024 + 256)]
|
||||||
|
[InlineData(200)]
|
||||||
|
public async void ClientCutScenarioTest(int objectSize)
|
||||||
|
{
|
||||||
|
var options = GetDefaultParams();
|
||||||
|
|
||||||
|
using var pool = new Pool(options);
|
||||||
|
|
||||||
|
var error = await pool.Dial(new CallContext()).ConfigureAwait(true);
|
||||||
|
|
||||||
|
Assert.Null(error);
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))))
|
||||||
|
{
|
||||||
|
WaitParams = lightWait
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerId = await pool.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var ctx = new CallContext
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(10),
|
||||||
|
Interceptors = new([new MetricsInterceptor()])
|
||||||
|
};
|
||||||
|
|
||||||
|
var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
|
||||||
|
|
||||||
|
Assert.NotNull(container);
|
||||||
|
|
||||||
|
byte[] bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await pool.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
bool hasObject = false;
|
||||||
|
await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
|
||||||
|
{
|
||||||
|
hasObject = true;
|
||||||
|
|
||||||
|
var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
||||||
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
|
Assert.Single(objHeader.Attributes);
|
||||||
|
Assert.Equal("fileName", objHeader.Attributes[0].Key);
|
||||||
|
Assert.Equal("test", objHeader.Attributes[0].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(hasObject);
|
||||||
|
|
||||||
|
var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId));
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
ReadOnlyMemory<byte>? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk.Value.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await CheckFilter(pool, containerId, new FilterByRootObject());
|
||||||
|
|
||||||
|
await Cleanup(pool);
|
||||||
|
|
||||||
|
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
IAsyncEnumerator<FrostFsContainerId>? enumerator = null;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (deadline <= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Assert.Fail("Containers exist");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
enumerator = pool.ListContainersAsync().GetAsyncEnumerator();
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
while (await enumerator!.MoveNextAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GetRandomBytes(int size)
|
||||||
|
{
|
||||||
|
Random rnd = new();
|
||||||
|
var bytes = new byte[size];
|
||||||
|
rnd.NextBytes(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IOptions<SingleOwnerClientSettings> GetSingleOwnerOptions(string key, string url)
|
||||||
|
{
|
||||||
|
return Options.Create(new SingleOwnerClientSettings
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Host = url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IOptions<ClientSettings> GetOptions(string url)
|
||||||
|
{
|
||||||
|
return Options.Create(new ClientSettings
|
||||||
|
{
|
||||||
|
Host = url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task Cleanup(Pool pool)
|
||||||
|
{
|
||||||
|
await foreach (var cid in pool.ListContainersAsync())
|
||||||
|
{
|
||||||
|
await pool.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,56 +1,11 @@
|
||||||
using System.Security.Cryptography;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ClientV2;
|
|
||||||
using FrostFS.SDK.Cryptography;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
public abstract class SessionTestsBase
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
{
|
|
||||||
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
|
||||||
|
|
||||||
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
protected ECDsa ECDsaKey { get; set; }
|
|
||||||
protected FrostFsOwner OwnerId { get; set; }
|
|
||||||
protected SessionMocker Mocker { get; set; }
|
|
||||||
|
|
||||||
protected SessionTestsBase()
|
|
||||||
{
|
|
||||||
Settings = Options.Create(new SingleOwnerClientSettings
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
Host = "http://localhost:8080"
|
|
||||||
});
|
|
||||||
|
|
||||||
ECDsaKey = key.LoadWif();
|
|
||||||
OwnerId = FrostFsOwner.FromKey(ECDsaKey);
|
|
||||||
|
|
||||||
Mocker = new SessionMocker(this.key)
|
|
||||||
{
|
|
||||||
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
|
||||||
Version = new FrostFsVersion(2, 13)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IFrostFSClient GetClient()
|
|
||||||
{
|
|
||||||
return ClientV2.FrostFSClient.GetTestInstance(
|
|
||||||
Settings,
|
|
||||||
null,
|
|
||||||
new NetworkMocker(this.key).GetMock().Object,
|
|
||||||
Mocker.GetMock().Object,
|
|
||||||
new ContainerMocker(this.key).GetMock().Object,
|
|
||||||
new ObjectMocker(this.key).GetMock().Object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SessionTest : SessionTestsBase
|
public class SessionTest : SessionTestsBase
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -64,7 +19,7 @@ public class SessionTest : SessionTestsBase
|
||||||
if (useContext)
|
if (useContext)
|
||||||
{
|
{
|
||||||
param.XHeaders.Add("headerKey1", "headerValue1");
|
param.XHeaders.Add("headerKey1", "headerValue1");
|
||||||
param.Context = new Context
|
param.Context = new CallContext
|
||||||
{
|
{
|
||||||
CancellationToken = Mocker.CancellationTokenSource.Token,
|
CancellationToken = Mocker.CancellationTokenSource.Token,
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
@ -101,7 +56,6 @@ public class SessionTest : SessionTestsBase
|
||||||
Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader);
|
Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader);
|
||||||
Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version);
|
Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version);
|
||||||
|
|
||||||
|
|
||||||
Assert.Null(Mocker.Metadata);
|
Assert.Null(Mocker.Metadata);
|
||||||
|
|
||||||
if (useContext)
|
if (useContext)
|
||||||
|
|
48
src/FrostFS.SDK.Tests/SessionTestsBase.cs
Normal file
48
src/FrostFS.SDK.Tests/SessionTestsBase.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
public abstract class SessionTestsBase
|
||||||
|
{
|
||||||
|
internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
|
||||||
|
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
|
||||||
|
|
||||||
|
protected ECDsa ECDsaKey { get; set; }
|
||||||
|
protected FrostFsOwner OwnerId { get; set; }
|
||||||
|
protected SessionMocker Mocker { get; set; }
|
||||||
|
|
||||||
|
protected SessionTestsBase()
|
||||||
|
{
|
||||||
|
Settings = Options.Create(new SingleOwnerClientSettings
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Host = "http://localhost:8080"
|
||||||
|
});
|
||||||
|
|
||||||
|
ECDsaKey = key.LoadWif();
|
||||||
|
OwnerId = FrostFsOwner.FromKey(ECDsaKey);
|
||||||
|
|
||||||
|
Mocker = new SessionMocker(this.key)
|
||||||
|
{
|
||||||
|
PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),
|
||||||
|
Version = new FrostFsVersion(2, 13)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IFrostFSClient GetClient()
|
||||||
|
{
|
||||||
|
return ClientV2.FrostFSClient.GetTestInstance(
|
||||||
|
Settings,
|
||||||
|
null,
|
||||||
|
new NetworkMocker(this.key).GetMock().Object,
|
||||||
|
Mocker.GetMock().Object,
|
||||||
|
new ContainerMocker(this.key).GetMock().Object,
|
||||||
|
new ObjectMocker(this.key).GetMock().Object);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,42 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
using static FrostFS.Session.SessionToken.Types.Body;
|
using static FrostFS.Session.SessionToken.Types.Body;
|
||||||
using FrostFS.SDK.ClientV2;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.SmokeTests;
|
namespace FrostFS.SDK.SmokeTests;
|
||||||
|
|
||||||
public class SmokeTests : SmokeTestsBase
|
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||||
|
[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")]
|
||||||
|
public class SmokeClientTests : SmokeTestsBase
|
||||||
{
|
{
|
||||||
private static readonly PrmWait lightWait = new(100, 1);
|
private static readonly PrmWait lightWait = new(100, 1);
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public async void AccountTest(bool isSingleOnwerClient)
|
||||||
|
{
|
||||||
|
using var client = isSingleOnwerClient
|
||||||
|
? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url))
|
||||||
|
: FrostFSClient.GetInstance(GetOptions(this.url));
|
||||||
|
|
||||||
|
PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
|
||||||
|
|
||||||
|
var result = await client.GetBalanceAsync(prm);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public async void NetworkMapTest(bool isSingleOnwerClient)
|
public async void NetworkMapTest(bool isSingleOnwerClient)
|
||||||
{
|
{
|
||||||
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
||||||
|
|
||||||
PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
|
PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
|
||||||
var result = await client.GetNetmapSnapshotAsync(prm);
|
var result = await client.GetNetmapSnapshotAsync(prm);
|
||||||
|
@ -41,7 +59,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public async void NodeInfoTest(bool isSingleOnwerClient)
|
public async void NodeInfoTest(bool isSingleOnwerClient)
|
||||||
{
|
{
|
||||||
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
||||||
|
|
||||||
PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
|
PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
|
||||||
|
|
||||||
|
@ -56,14 +74,14 @@ public class SmokeTests : SmokeTestsBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NodeInfo_Statistics_Test()
|
public async void NodeInfoStatisticsTest()
|
||||||
{
|
{
|
||||||
var ctx = new Context
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
|
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
|
||||||
};
|
};
|
||||||
|
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
var result = await client.GetNodeInfoAsync();
|
var result = await client.GetNodeInfoAsync();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +91,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public async void GetSessionTest(bool isSingleOnwerClient)
|
public async void GetSessionTest(bool isSingleOnwerClient)
|
||||||
{
|
{
|
||||||
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url));
|
||||||
|
|
||||||
PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx };
|
PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx };
|
||||||
|
|
||||||
|
@ -81,7 +99,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var session = new Session.SessionToken().Deserialize(token.Token);
|
var session = new Session.SessionToken().Deserialize(token.Token);
|
||||||
|
|
||||||
var ownerHash = Base58.Decode(OwnerId.Value);
|
var ownerHash = Base58.Decode(OwnerId!.Value);
|
||||||
|
|
||||||
Assert.NotNull(session);
|
Assert.NotNull(session);
|
||||||
Assert.Null(session.Body.Container);
|
Assert.Null(session.Body.Container);
|
||||||
|
@ -96,7 +114,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void CreateObjectWithSessionToken()
|
public async void CreateObjectWithSessionToken()
|
||||||
{
|
{
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
|
@ -144,11 +162,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void FilterTest()
|
public async void FilterTest()
|
||||||
{
|
{
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
//var prm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Namespace, "root"));
|
|
||||||
|
|
||||||
//var chains = await client.ListChainAsync(prm);
|
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
|
@ -184,7 +198,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
||||||
|
|
||||||
var ecdsaKey = this.key.LoadWif();
|
var ecdsaKey = this.keyString.LoadWif();
|
||||||
|
|
||||||
var networkInfo = await client.GetNetmapSnapshotAsync();
|
var networkInfo = await client.GetNetmapSnapshotAsync();
|
||||||
|
|
||||||
|
@ -192,7 +206,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey)));
|
await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey)));
|
||||||
|
|
||||||
await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split.SplitId));
|
await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId));
|
||||||
|
|
||||||
await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"));
|
await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"));
|
||||||
|
|
||||||
|
@ -232,12 +246,12 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[InlineData(6 * 1024 * 1024 + 100)]
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
public async void SimpleScenarioTest(int objectSize)
|
public async void SimpleScenarioTest(int objectSize)
|
||||||
{
|
{
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
bool callbackInvoked = false;
|
bool callbackInvoked = false;
|
||||||
var ctx = new Context
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
// Timeout = TimeSpan.FromSeconds(20),
|
// Timeout = TimeSpan.FromSeconds(20),
|
||||||
Callback = new((CallStatistics cs) =>
|
Callback = new((CallStatistics cs) =>
|
||||||
|
@ -269,7 +283,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
Context = new Context
|
Context = new CallContext
|
||||||
{
|
{
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
}
|
}
|
||||||
|
@ -286,6 +300,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId));
|
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId));
|
||||||
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
Assert.Single(objHeader.Attributes);
|
Assert.Single(objHeader.Attributes);
|
||||||
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
Assert.Equal("test", objHeader.Attributes.First().Value);
|
Assert.Equal("test", objHeader.Attributes.First().Value);
|
||||||
|
@ -304,7 +319,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
ms.Write(chunk.Value.Span);
|
ms.Write(chunk.Value.Span);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
|
@ -320,13 +335,13 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[InlineData(6 * 1024 * 1024 + 100)]
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
public async void SimpleScenarioWithSessionTest(int objectSize)
|
public async void SimpleScenarioWithSessionTest(int objectSize)
|
||||||
{
|
{
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
|
var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
var ctx = new Context
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
@ -353,7 +368,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[new FrostFsAttributePair("fileName", "test")]),
|
[new FrostFsAttributePair("fileName", "test")]),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
Context = new Context
|
Context = new CallContext
|
||||||
{
|
{
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
},
|
},
|
||||||
|
@ -371,6 +386,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token });
|
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token });
|
||||||
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
Assert.Single(objHeader.Attributes);
|
Assert.Single(objHeader.Attributes);
|
||||||
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
Assert.Equal("test", objHeader.Attributes.First().Value);
|
Assert.Equal("test", objHeader.Attributes.First().Value);
|
||||||
|
@ -389,7 +405,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
ms.Write(chunk.Value.Span);
|
ms.Write(chunk.Value.Span);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
|
@ -408,7 +424,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
[InlineData(200)]
|
[InlineData(200)]
|
||||||
public async void ClientCutScenarioTest(int objectSize)
|
public async void ClientCutScenarioTest(int objectSize)
|
||||||
{
|
{
|
||||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
|
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
|
@ -419,7 +435,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var containerId = await client.CreateContainerAsync(createContainerParam);
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
var ctx = new Context
|
var ctx = new CallContext
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10),
|
Timeout = TimeSpan.FromSeconds(10),
|
||||||
Interceptors = new([new MetricsInterceptor()])
|
Interceptors = new([new MetricsInterceptor()])
|
||||||
|
@ -452,6 +468,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
|
|
||||||
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
||||||
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.NotNull(objHeader.Attributes);
|
||||||
Assert.Single(objHeader.Attributes);
|
Assert.Single(objHeader.Attributes);
|
||||||
Assert.Equal("fileName", objHeader.Attributes[0].Key);
|
Assert.Equal("fileName", objHeader.Attributes[0].Key);
|
||||||
Assert.Equal("test", objHeader.Attributes[0].Value);
|
Assert.Equal("test", objHeader.Attributes[0].Value);
|
||||||
|
@ -470,7 +487,7 @@ public class SmokeTests : SmokeTestsBase
|
||||||
ms.Write(chunk.Value.Span);
|
ms.Write(chunk.Value.Span);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||||
|
|
||||||
await CheckFilter(client, containerId, new FilterByRootObject());
|
await CheckFilter(client, containerId, new FilterByRootObject());
|
||||||
|
|
|
@ -7,24 +7,24 @@ namespace FrostFS.SDK.SmokeTests;
|
||||||
|
|
||||||
public abstract class SmokeTestsBase
|
public abstract class SmokeTestsBase
|
||||||
{
|
{
|
||||||
protected readonly string key = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
|
internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
|
||||||
|
|
||||||
protected readonly string url = "http://172.23.32.4:8080";
|
internal readonly string url = "http://172.23.32.4:8080";
|
||||||
|
|
||||||
protected ECDsa Key { get; }
|
protected ECDsa? Key { get; }
|
||||||
|
|
||||||
protected FrostFsOwner OwnerId { get; }
|
protected FrostFsOwner? OwnerId { get; }
|
||||||
|
|
||||||
protected FrostFsVersion Version { get; }
|
protected FrostFsVersion? Version { get; }
|
||||||
|
|
||||||
protected Context Ctx { get; }
|
protected CallContext? Ctx { get; }
|
||||||
|
|
||||||
protected SmokeTestsBase()
|
protected SmokeTestsBase()
|
||||||
{
|
{
|
||||||
Key = key.LoadWif();
|
Key = keyString.LoadWif();
|
||||||
OwnerId = FrostFsOwner.FromKey(Key);
|
OwnerId = FrostFsOwner.FromKey(Key);
|
||||||
Version = new FrostFsVersion(2, 13);
|
Version = new FrostFsVersion(2, 13);
|
||||||
|
|
||||||
Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version };
|
Ctx = new CallContext { Key = Key, OwnerId = OwnerId, Version = Version };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue