Compare commits

...

2 commits

Author SHA1 Message Date
2aa1b4382f tests 2024-08-16 10:38:50 +03:00
c2501cc112 mulitenant client 2024-08-12 04:28:17 +03:00
36 changed files with 910 additions and 301 deletions

View file

@ -35,6 +35,11 @@ public class Client : IFrostFSClient
return new Client(clientOptions, channelOptions);
}
public static IFrostFSClient GetSingleOwnerInstance(IOptions<SingleOwnerClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
{
return new Client(clientOptions, channelOptions);
}
/// <summary>
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
/// </summary>
@ -46,7 +51,7 @@ public class Client : IFrostFSClient
/// <param name="objectService">Object.ObjectService.ObjectServiceClient implementation</param>
/// <returns></returns>
public static IFrostFSClient GetTestInstance(
IOptions<ClientSettings> clientOptions,
IOptions<SingleOwnerClientSettings> clientOptions,
GrpcChannelOptions? channelOptions,
NetmapService.NetmapServiceClient netmapService,
SessionService.SessionServiceClient sessionService,
@ -57,7 +62,7 @@ public class Client : IFrostFSClient
}
private Client(
IOptions<ClientSettings> settings,
IOptions<SingleOwnerClientSettings> settings,
GrpcChannelOptions? channelOptions,
ContainerService.ContainerServiceClient containerService,
NetmapService.NetmapServiceClient netmapService,
@ -68,7 +73,7 @@ public class Client : IFrostFSClient
OwnerId.FromKey(ecdsaKey);
ClientCtx = new ClientEnvironment(
this,
client: this,
key: ecdsaKey,
owner: OwnerId.FromKey(ecdsaKey),
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
@ -86,6 +91,25 @@ public class Client : IFrostFSClient
clientSettings.Validate();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new ClientEnvironment(
this,
key: null,
owner: null,
channel: channel,
version: new Version(2, 13));
// TODO: define timeout logic
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
}
private Client(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
clientSettings.Validate();
var ecdsaKey = clientSettings.Key.LoadWif();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
@ -221,22 +245,22 @@ public class Client : IFrostFSClient
#endregion
#region ToolsImplementation
public ObjectId CalculateObjectId(ObjectHeader header)
public ObjectId CalculateObjectId(ObjectHeader header, Context ctx)
{
if (header == null)
throw new ArgumentNullException(nameof(header));
return ObjectTools.CalculateObjectId(header, ClientCtx);
return ObjectTools.CalculateObjectId(header, ctx);
}
#endregion
private async void CheckFrostFsVersionSupport(Context? ctx = default)
{
var args = new PrmNodeInfo(ctx);
var args = new PrmNodeInfo { Context = ctx };
var service = GetNetmapService(args);
var localNodeInfo = await service.GetLocalNodeInfoAsync(args);
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
if (!localNodeInfo.Version.IsSupported(args.Context!.Version))
{
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
throw new ApplicationException(msg);
@ -250,6 +274,31 @@ public class Client : IFrostFSClient
ctx.Context ??= new Context();
if (ctx.Context.Key == null)
{
if (ClientCtx.Key == null)
{
throw new Exception("Key is not initialized.");
}
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
}
if (ctx.Context.OwnerId == null)
{
ctx.Context.OwnerId = ClientCtx.Owner ?? OwnerId.FromKey(ctx.Context.Key);
}
if (ctx.Context.Version == null)
{
if (ClientCtx.Version == null)
{
throw new Exception("Version is not initialized.");
}
ctx.Context.Version = ClientCtx.Version;
}
CallInvoker? callInvoker = null;
if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0)
{

View file

@ -45,6 +45,6 @@ public interface IFrostFSClient : IDisposable
#endregion
#region Tools
ObjectId CalculateObjectId(ObjectHeader header);
ObjectId CalculateObjectId(ObjectHeader header, Context ctx);
#endregion
}

View file

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
using Grpc.Core.Interceptors;
namespace FrostFS.SDK.ClientV2;
@ -10,8 +13,18 @@ public class Context()
{
private List<Interceptor>? interceptors;
private ByteString publicKeyCache;
public ECDsa Key { get; set; }
public OwnerId OwnerId { get; set; }
public ModelsV2.Version Version { get; set; }
public CancellationToken CancellationToken { get; set; } = default;
public TimeSpan Timeout { get; set; } = default;
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
public Action<CallStatistics>? Callback { get; set; }
@ -21,4 +34,17 @@ public class Context()
get { return this.interceptors ??= []; }
set { this.interceptors = value; }
}
public ByteString PublicKeyCache
{
get
{
if (publicKeyCache == null)
{
publicKeyCache = ByteString.CopyFrom(Key.PublicKey());
}
return publicKeyCache;
}
}
}

View file

@ -0,0 +1,11 @@
using FrostFS.SDK.ModelsV2;
using System.Security.Cryptography;
namespace FrostFS.SDK.ClientV2.Parameters;
public class Credentials(ECDsa key, OwnerId ownerId)
{
public ECDsa Key { get; } = key;
public OwnerId OwnerId { get; } = ownerId;
}

View file

@ -0,0 +1,14 @@
using System.Collections.Specialized;
namespace FrostFS.SDK.ClientV2.Parameters;
public class PrmBase() : IContext
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
}

View file

@ -1,8 +1,8 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmContainerCreate(ModelsV2.Container container) : IContext
public sealed class PrmContainerCreate(ModelsV2.Container container) : PrmBase, ISessionToken
{
public ModelsV2.Container Container { get; set; } = container;
@ -12,11 +12,7 @@ public sealed class PrmContainerCreate(ModelsV2.Container container) : IContext
/// <value>Rules for polling the result</value>
public PrmWait? WaitParams { get; set; }
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
public string SessionKey { get; set; }
/// <inheritdoc />
public Context? Context { get; set; }
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,9 +1,8 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = null) : IContext
public sealed class PrmContainerDelete(ContainerId containerId) : PrmBase, ISessionToken
{
public ContainerId ContainerId { get; set; } = containerId;
@ -13,11 +12,5 @@ public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = n
/// <value>Rules for polling the result</value>
public PrmWait? WaitParams { get; set; }
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = ctx;
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,17 +1,8 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmContainerGet(ContainerId containerId, Context? ctx = null) : IContext
public sealed class PrmContainerGet(ContainerId containerId) : PrmBase
{
public ContainerId ContainerId { get; set; } = containerId;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = ctx;
}

View file

@ -2,13 +2,6 @@
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmContainerGetAll() : IContext
public sealed class PrmContainerGetAll() : PrmBase()
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
}

View file

@ -2,13 +2,6 @@
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmNetmapSnapshot(Context? context = default) : IContext
public sealed class PrmNetmapSnapshot() : PrmBase
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
}

View file

@ -1,14 +1,5 @@
using System.Collections.Specialized;
namespace FrostFS.SDK.ClientV2.Parameters;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmNetworkSettings(Context? context = default) : IContext
public sealed class PrmNetworkSettings() : PrmBase
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
}

View file

@ -1,14 +1,5 @@
using System.Collections.Specialized;
namespace FrostFS.SDK.ClientV2.Parameters;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmNodeInfo(Context? context = default) : IContext
public sealed class PrmNodeInfo() : PrmBase
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
}

View file

@ -3,19 +3,12 @@ using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken
public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken
{
public ContainerId ContainerId { get; set; } = containerId;
public ObjectId ObjectId { get; set; } = objectId;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,21 +1,13 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken
public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken
{
public ContainerId ContainerId { get; set; } = containerId;
public ObjectId ObjectId { get; set; } = objectId;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,21 +1,13 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken
public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken
{
public ContainerId ContainerId { get; set; } = containerId;
public ObjectId ObjectId { get; set; } = objectId;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,9 +1,9 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using System.IO;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmObjectPut : IContext, ISessionToken
public sealed class PrmObjectPut : PrmBase, ISessionToken
{
/// <summary>
/// Need to provide values like <c>ContainerId</c> and <c>ObjectType</c> to create and object.
@ -32,12 +32,9 @@ public sealed class PrmObjectPut : IContext, ISessionToken
public int BufferMaxSize { get; set; }
/// <summary>
/// FrostFS request X-Headers
/// Allows to define a buffer for chunks to manage by the memory allocation and releasing.
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
public byte[]? CustomBuffer { get; set; }
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }

View file

@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
using System.Collections.Generic;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : IContext, ISessionToken
public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken
{
/// <summary>
/// Defines container for the search
@ -17,14 +16,6 @@ public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilte
/// <value>Collection of filters</value>
public IEnumerable<IObjectFilter> Filters { get; set; } = filters;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; }
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }
}

View file

@ -1,16 +1,6 @@
using System.Collections.Specialized;
namespace FrostFS.SDK.ClientV2.Parameters;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmSessionCreate(ulong expiration, Context? context = default) : IContext
public sealed class PrmSessionCreate(ulong expiration) : PrmBase
{
public ulong Expiration { get; set; } = expiration;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
}

View file

@ -1,19 +1,11 @@
using System.Collections.Specialized;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.ClientV2.Parameters;
public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, Context? context = null) : IContext, ISessionToken
public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken
{
public FrostFsObject FrostFsObject { get; set; } = frostFsObject;
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
/// <inheritdoc />
public SessionToken? SessionToken { get; set; }
}

View file

@ -17,7 +17,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{
internal async Task<ModelsV2.Container> GetContainerAsync(PrmContainerGet args)
{
GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders);
GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders, args.Context!);
var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
@ -34,12 +34,12 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{
Body = new ListRequest.Types.Body
{
OwnerId = Context.Owner.ToMessage()
OwnerId = ctx.OwnerId.ToMessage()
}
};
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -55,20 +55,20 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{
var ctx = args.Context!;
var grpcContainer = args.Container.ToMessage();
grpcContainer.OwnerId = Context.Owner.ToMessage();
grpcContainer.Version = Context.Version.ToMessage();
grpcContainer.OwnerId = ctx.OwnerId.ToMessage();
grpcContainer.Version = ctx.Version.ToMessage();
var request = new PutRequest
{
Body = new PutRequest.Types.Body
{
Container = grpcContainer,
Signature = Context.Key.ECDsaKey.SignRFC6979(grpcContainer)
Signature = ctx.Key.SignRFC6979(grpcContainer)
}
};
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -87,13 +87,13 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
Body = new DeleteRequest.Types.Body
{
ContainerId = args.ContainerId.ToMessage(),
Signature = Context.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value)
Signature = ctx.Key.SignRFC6979(args.ContainerId.ToMessage().Value)
}
};
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -102,7 +102,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
Verifier.CheckResponse(response);
}
private GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders)
private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx)
{
var request = new GetRequest
{
@ -113,7 +113,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
};
request.AddMetaHeader(xHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
return request;
}
@ -126,7 +126,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx)
{
var request = GetContainerRequest(id, null);
var request = GetContainerRequest(id, null, ctx);
async Task action()
{

View file

@ -49,7 +49,7 @@ internal class NetmapServiceProvider : ContextAccessor
};
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -63,7 +63,7 @@ internal class NetmapServiceProvider : ContextAccessor
var request = new NetworkInfoRequest();
request.AddMetaHeader(null);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -79,7 +79,7 @@ internal class NetmapServiceProvider : ContextAccessor
var request = new NetmapSnapshotRequest();
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken);

View file

@ -46,11 +46,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
request.Body.Address,
ObjectSessionContext.Types.Verb.Head,
Context.Key.ECDsaKey);
ctx.Key);
request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -80,11 +80,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
request.Body.Address,
ObjectSessionContext.Types.Verb.Get,
Context.Key.ECDsaKey);
ctx.Key);
request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
return await GetObject(request, ctx);
}
@ -109,10 +109,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
request.Body.Address,
ObjectSessionContext.Types.Verb.Delete,
Context.Key.ECDsaKey);
ctx.Key);
request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -139,11 +139,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
new Address { ContainerId = request.Body.ContainerId },
ObjectSessionContext.Types.Verb.Search,
Context.Key.ECDsaKey);
ctx.Key);
request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var objectsIds = SearchObjects(request, ctx);
@ -177,7 +177,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
internal async Task<ObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
{
var ctx = args.Context!;
var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, env);
var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx);
var request = new PutSingleRequest
{
@ -192,11 +192,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId},
ObjectSessionContext.Types.Verb.Put,
Context.Key.ECDsaKey);
ctx.Key);
request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -226,7 +226,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
if (args.MaxObjectSizeCache == 0)
{
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx));
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx });
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
}
@ -337,7 +337,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
}
};
chunkRequest.Sign(Context.Key.ECDsaKey);
chunkRequest.Sign(ctx.Key);
await stream.Write(chunkRequest);
}
@ -352,14 +352,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
{
var header = args.Header!;
header.OwnerId = Context.Owner;
header.Version = Context.Version;
header.OwnerId = ctx.OwnerId;
header.Version = ctx.Version;
var grpcHeader = header.ToMessage();
if (header.Split != null)
{
ObjectTools.SetSplitValues(grpcHeader, header.Split, env);
ObjectTools.SetSplitValues(grpcHeader, header.Split, ctx);
}
var oid = new ObjectID { Value = grpcHeader.Sha256() };
@ -380,12 +380,12 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext(
new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid },
ObjectSessionContext.Types.Verb.Put,
Context.Key.ECDsaKey
ctx.Key
);
initRequest.AddMetaHeader(args.XHeaders, sessionToken);
initRequest.Sign(Context.Key.ECDsaKey);
initRequest.Sign(ctx.Key);
return await PutObjectInit(initRequest, ctx);
}

View file

@ -17,17 +17,18 @@ internal class SessionServiceProvider : ContextAccessor
internal async Task<SessionToken> CreateSessionAsync(PrmSessionCreate args)
{
var ctx = args.Context!;
var request = new CreateRequest
{
Body = new CreateRequest.Types.Body
{
OwnerId = Context.Owner.ToMessage(),
OwnerId = ctx.OwnerId.ToMessage(),
Expiration = args.Expiration
}
};
request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key.ECDsaKey);
request.Sign(ctx.Key);
return await CreateSession(request, args.Context!);
}

View file

@ -15,7 +15,7 @@ internal class SessionProvider(ClientEnvironment env)
{
if (args.SessionToken is null)
{
return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx));
return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx });
}
return new Session.SessionToken().Deserialize(args.SessionToken.Token);

View file

@ -1,22 +1,23 @@
using FrostFS.SDK.ModelsV2;
using Google.Protobuf;
using Grpc.Net.Client;
using System;
using System.Security.Cryptography;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK.ClientV2;
public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable
public class ClientEnvironment(Client client, ECDsa? key, OwnerId? owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable
{
internal OwnerId Owner { get; } = owner;
internal OwnerId? Owner { get; } = owner;
internal GrpcChannel Channel { get; private set; } = channel;
internal ModelsV2.Version Version { get; } = version;
internal NetworkSettings? NetworkSettings { get; set; }
internal Client Client { get; } = client;
internal ClientKey Key { get; } = new ClientKey(key);
internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null;
public void Dispose()
{

View file

@ -7,26 +7,25 @@ using FrostFS.Refs;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2;
using static FrostFS.Object.Header.Types;
namespace FrostFS.SDK.ClientV2;
internal class ObjectTools
{
internal static ObjectId CalculateObjectId(ObjectHeader header, ClientEnvironment env)
internal static ObjectId CalculateObjectId(ObjectHeader header, Context ctx)
{
var grpcHeader = CreateHeader(header, [], env);
var grpcHeader = CreateHeader(header, [], ctx);
if (header.Split != null)
SetSplitValues(grpcHeader, header.Split, env);
SetSplitValues(grpcHeader, header.Split, ctx);
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
}
internal static Object.Object CreateObject(FrostFsObject @object, ClientEnvironment env)
internal static Object.Object CreateObject(FrostFsObject @object, Context ctx)
{
@object.Header.OwnerId = env.Owner;
@object.Header.Version = env.Version;
@object.Header.OwnerId = ctx.OwnerId;
@object.Header.Version = ctx.Version;
var grpcHeader = @object.Header.ToMessage();
@ -36,7 +35,7 @@ internal class ObjectTools
var split = @object.Header.Split;
if (split != null)
{
SetSplitValues(grpcHeader, split, env);
SetSplitValues(grpcHeader, split, ctx);
}
var obj = new Object.Object
@ -48,14 +47,14 @@ internal class ObjectTools
obj.Signature = new Refs.Signature
{
Key = env.Key.PublicKeyProto,
Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(obj.ObjectId.ToByteArray())),
Key = ctx.PublicKeyCache,
Sign = ByteString.CopyFrom(ctx.Key.SignData(obj.ObjectId.ToByteArray())),
};
return obj;
}
internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, ClientEnvironment env)
internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, Context ctx)
{
grpcHeader.Split = new Header.Types.Split
{
@ -67,14 +66,14 @@ internal class ObjectTools
if (split.ParentHeader is not null)
{
var grpcParentHeader = CreateHeader(split.ParentHeader, [], env);
var grpcParentHeader = CreateHeader(split.ParentHeader, [], ctx);
grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() };
grpcHeader.Split.ParentHeader = grpcParentHeader;
grpcHeader.Split.ParentSignature = new Refs.Signature
{
Key = env.Key.PublicKeyProto,
Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray())),
Key = ctx.PublicKeyCache,
Sign = ByteString.CopyFrom(ctx.Key.SignData(grpcHeader.Split.Parent.ToByteArray())),
};
split.Parent = grpcHeader.Split.Parent.ToModel();
@ -83,12 +82,12 @@ internal class ObjectTools
grpcHeader.Split.Previous = split.Previous?.ToMessage();
}
internal static Header CreateHeader(ObjectHeader header, byte[]? payload, ClientEnvironment env)
internal static Header CreateHeader(ObjectHeader header, byte[]? payload, Context ctx)
{
var grpcHeader = header.ToMessage();
grpcHeader.OwnerId = env.Owner.ToMessage();
grpcHeader.Version = env.Version.ToMessage();
grpcHeader.OwnerId = ctx.OwnerId.ToMessage();
grpcHeader.Version = ctx.Version.ToMessage();
if (payload != null) // && payload.Length > 0
grpcHeader.PayloadHash = Sha256Checksum(payload);

View file

@ -49,4 +49,22 @@ public static class RequestConstructor
sessionToken.Signature = key.SignMessagePart(sessionToken.Body);
}
public static void CreateContainerTokenContext(this Session.SessionToken sessionToken,
ContainerID? containerId,
ContainerSessionContext.Types.Verb verb,
ECDsa key)
{
if (sessionToken.Body.Container?.ContainerId != null)
return;
sessionToken.Body.Container = new() { Verb = verb };
if (containerId != null)
sessionToken.Body.Container.ContainerId = containerId;
else
sessionToken.Body.Container.Wildcard = true;
sessionToken.Signature = key.SignMessagePart(sessionToken.Body);
}
}

View file

@ -1,28 +1,64 @@
using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.Text;
namespace FrostFS.SDK.ModelsV2;
public class ClientSettings
{
private static readonly string errorTemplate = "{0} is required parameter";
public string Key { get; set; } = string.Empty;
protected static readonly string errorTemplate = "{0} is required parameter";
public string Host { get; set; } = string.Empty;
public void Validate()
public virtual void Validate()
{
StringBuilder? error = null;
if (string.IsNullOrWhiteSpace(Key))
(error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Key)));
if (string.IsNullOrWhiteSpace(Host))
(error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Host)));
if (error != null)
throw new ArgumentException(error.ToString());
var errors = CheckFields();
if (errors != null)
ThrowException(errors);
}
protected List<string>? CheckFields()
{
List<string>? errors = null;
if (string.IsNullOrWhiteSpace(Host))
(errors ??= []).Add(string.Format(errorTemplate, nameof(Host)));
return errors;
}
protected static void ThrowException(List<string> errors)
{
StringBuilder messages = new();
foreach (var error in errors)
{
messages.AppendLine(error);
}
throw new ArgumentException(messages.ToString());
}
}
public class SingleOwnerClientSettings : ClientSettings
{
public string Key { get; set; } = string.Empty;
public override void Validate()
{
var errors = CheckFields();
if (errors != null)
ThrowException(errors);
}
protected List<string>? CheckFields()
{
List<string>? errors = base.CheckFields();
if (string.IsNullOrWhiteSpace(Key))
(errors ??= []).Add(string.Format(errorTemplate, nameof(Key)));
return errors;
}
}

View file

@ -16,12 +16,12 @@ public abstract class ContainerTestsBase
{
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected IOptions<ClientSettings> Settings { get; set; }
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
protected ContainerMocker Mocker { get; set; }
protected ContainerTestsBase()
{
Settings = Options.Create(new ClientSettings
Settings = Options.Create(new SingleOwnerClientSettings
{
Key = key,
Host = "http://localhost:8080"

View file

@ -27,6 +27,12 @@ public abstract class ServiceBase(string key)
public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW;
public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1));
public Metadata Metadata { get; protected set; }
public DateTime? DateTime { get; protected set; }
public CancellationToken CancellationToken { get; protected set; }
public CancellationTokenSource CancellationTokenSource { get; protected set; } = new CancellationTokenSource();
public static Metadata ResponseMetaData => [];
protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response)

View file

@ -3,24 +3,40 @@ using FrostFS.Netmap;
using Grpc.Core;
using FrostFS.SDK.ClientV2;
using Google.Protobuf;
using FrostFS.SDK.ModelsV2;
namespace FrostFS.SDK.Tests;
public class NetworkMocker(string key) : ServiceBase(key)
{
private static readonly string[] parameterKeys = [
internal static readonly string[] ParameterKeys = [
"AuditFee",
"BasicIncomeRate",
"ContainerFee",
"ContainerAliasFee",
"EpochDuration",
"IRCandidateFee",
"InnerRingCandidateFee",
"MaxECDataCount",
"MaxECParityCount",
"MaxObjectSize",
"WithdrawalFee",
"WithdrawFee",
"HomomorphicHashingDisabled",
"MaintenanceModeAllowed" ];
"MaintenanceModeAllowed"
];
public Dictionary<string, byte[]>? Parameters { get; set; }
public LocalNodeInfoResponse NodeInfoResponse { get; set; }
public LocalNodeInfoRequest LocalNodeInfoRequest { get; set; }
public NetworkInfoRequest NetworkInfoRequest { get; set; }
public NetmapSnapshotResponse NetmapSnapshotResponse { get; set; }
public NetmapSnapshotRequest NetmapSnapshotRequest { get; set; }
public Mock<NetmapService.NetmapServiceClient> GetMock()
{
var mock = new Mock<NetmapService.NetmapServiceClient>();
@ -29,7 +45,7 @@ public class NetworkMocker(string key) : ServiceBase(key)
var networkConfig = new NetworkConfig();
foreach (var key in parameterKeys)
foreach (var key in ParameterKeys)
{
networkConfig.Parameters.Add(new NetworkConfig.Types.Parameter
{
@ -62,7 +78,10 @@ public class NetworkMocker(string key) : ServiceBase(key)
It.IsAny<CancellationToken>()))
.Returns((NetworkInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
{
Verifier.CheckRequest(r);
NetworkInfoRequest = r;
Metadata = m;
DateTime = dt;
CancellationToken = ct;
return new AsyncUnaryCall<NetworkInfoResponse>(
Task.FromResult(response),
@ -72,6 +91,58 @@ public class NetworkMocker(string key) : ServiceBase(key)
() => { });
});
if (NodeInfoResponse != null)
{
NodeInfoResponse.MetaHeader = ResponseMetaHeader;
NodeInfoResponse.VerifyHeader = GetResponseVerificationHeader(NodeInfoResponse);
mock.Setup(x => x.LocalNodeInfoAsync(
It.IsAny<LocalNodeInfoRequest>(),
It.IsAny<Metadata>(),
It.IsAny<DateTime?>(),
It.IsAny<CancellationToken>()))
.Returns((LocalNodeInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
{
LocalNodeInfoRequest = r;
Metadata = m;
DateTime = dt;
CancellationToken = ct;
return new AsyncUnaryCall<LocalNodeInfoResponse>(
Task.FromResult(NodeInfoResponse),
Task.FromResult(ResponseMetaData),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
() => ResponseMetaData,
() => { });
});
}
if (NetmapSnapshotResponse != null)
{
NetmapSnapshotResponse.MetaHeader = ResponseMetaHeader;
NetmapSnapshotResponse.VerifyHeader = GetResponseVerificationHeader(NetmapSnapshotResponse);
mock.Setup(x => x.NetmapSnapshotAsync(
It.IsAny<NetmapSnapshotRequest>(),
It.IsAny<Metadata>(),
It.IsAny<DateTime?>(),
It.IsAny<CancellationToken>()))
.Returns((NetmapSnapshotRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
{
NetmapSnapshotRequest = r;
Metadata = m;
DateTime = dt;
CancellationToken = ct;
return new AsyncUnaryCall<NetmapSnapshotResponse>(
Task.FromResult(NetmapSnapshotResponse),
Task.FromResult(ResponseMetaData),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
() => ResponseMetaData,
() => { });
});
}
return mock;
}
}

View file

@ -1,8 +1,7 @@
using Moq;
using FrostFS.Session;
using Grpc.Core;
using FrostFS.SDK.ClientV2;
using Google.Protobuf;
using Grpc.Core;
using Moq;
namespace FrostFS.SDK.Tests;
@ -12,23 +11,32 @@ public class SessionMocker(string key) : ServiceBase(key)
public byte[]? SessionKey { get; set; }
public CreateRequest CreateSessionRequest { get; private set; }
public Mock<SessionService.SessionServiceClient> GetMock()
{
var mock = new Mock<SessionService.SessionServiceClient>();
Random rand = new();
SessionId = new byte[32];
SessionKey = new byte[32];
if (SessionId == null)
{
SessionId = new byte[32];
rand.NextBytes(SessionId);
}
if (SessionKey == null)
{
SessionKey = new byte[32];
rand.NextBytes(SessionKey);
}
CreateResponse response = new()
{
Body = new CreateResponse.Types.Body
{
Id = ByteString.CopyFrom(SessionId),
SessionKey = ByteString.CopyFrom(SessionId)
SessionKey = ByteString.CopyFrom(SessionKey)
},
MetaHeader = ResponseMetaHeader
};
@ -42,7 +50,10 @@ public class SessionMocker(string key) : ServiceBase(key)
It.IsAny<CancellationToken>()))
.Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
{
Verifier.CheckRequest(r);
CreateSessionRequest = r;
Metadata = m;
DateTime = dt;
CancellationToken = ct;
return new AsyncUnaryCall<CreateResponse>(
Task.FromResult(response),

View file

@ -0,0 +1,303 @@
using FrostFS.Netmap;
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Parameters;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2.Enums;
using Google.Protobuf;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
using System.Threading;
namespace FrostFS.SDK.Tests;
public abstract class NetworkTestsBase
{
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
protected ModelsV2.Version Version { get; set; } = new ModelsV2.Version(2, 13);
protected ECDsa ECDsaKey { get; set; }
protected OwnerId 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 = OwnerId.FromKey(ECDsaKey);
Mocker = new NetworkMocker(this.key);
}
protected IFrostFSClient GetClient()
{
return ClientV2.Client.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
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NetworkSettingsTest(bool useContext)
{
Mocker.Parameters = new Dictionary<string, byte[]>
{
{ "AuditFee", [1] },
{ "BasicIncomeRate", [2] },
{ "ContainerFee", [3] },
{ "ContainerAliasFee", [4] },
{ "EpochDuration", [5] },
{ "InnerRingCandidateFee", [6] },
{ "MaxECDataCount", [7] },
{ "MaxECParityCount", [8] },
{ "MaxObjectSize", [9] },
{ "WithdrawFee", [10] },
{ "HomomorphicHashingDisabled", [1] },
{ "MaintenanceModeAllowed", [1] },
};
var param = new PrmNetworkSettings();
if (useContext)
{
param.Context = new Context
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Version
};
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
var result = await GetClient().GetNetworkSettingsAsync(param);
var validTimeoutTo = DateTime.UtcNow.AddSeconds(20);
Assert.NotNull(result);
Assert.Equal(Mocker.Parameters["AuditFee"], [(byte)result.AuditFee]);
Assert.Equal(Mocker.Parameters["BasicIncomeRate"], [(byte)result.BasicIncomeRate]);
Assert.Equal(Mocker.Parameters["ContainerFee"], [(byte)result.ContainerFee]);
Assert.Equal(Mocker.Parameters["ContainerAliasFee"], [(byte)result.ContainerAliasFee]);
Assert.Equal(Mocker.Parameters["EpochDuration"], [(byte)result.EpochDuration]);
Assert.Equal(Mocker.Parameters["InnerRingCandidateFee"], [(byte)result.InnerRingCandidateFee]);
Assert.Equal(Mocker.Parameters["MaxECDataCount"], [(byte)result.MaxECDataCount]);
Assert.Equal(Mocker.Parameters["MaxECParityCount"], [(byte)result.MaxECParityCount]);
Assert.Equal(Mocker.Parameters["MaxObjectSize"], [(byte)result.MaxObjectSize]);
Assert.Equal(Mocker.Parameters["WithdrawFee"], [(byte)result.WithdrawFee]);
Assert.True(result.HomomorphicHashingDisabled);
Assert.True(result.MaintenanceModeAllowed);
if (useContext)
{
Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken);
Assert.NotNull(Mocker.DateTime);
Assert.True(Mocker.DateTime.Value >= validTimeoutFrom);
Assert.True(Mocker.DateTime.Value <= validTimeoutTo);
}
else
{
Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NetmapSnapshotTest(bool useContext)
{
var body = new NetmapSnapshotResponse.Types.Body
{
Netmap = new Netmap.Netmap { Epoch = 99 }
};
var nodeInfo1 = new NodeInfo
{
State = NodeInfo.Types.State.Online,
PublicKey = ByteString.CopyFrom([1, 2, 3])
};
nodeInfo1.Addresses.Add("address1");
nodeInfo1.Addresses.Add("address2");
nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"});
nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" });
var nodeInfo2 = new NodeInfo
{
State = NodeInfo.Types.State.Offline,
PublicKey = ByteString.CopyFrom([3,4,5])
};
nodeInfo2.Addresses.Add("address3");
nodeInfo2.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key3", Value = "value3" });
body.Netmap.Nodes.Add(nodeInfo1);
body.Netmap.Nodes.Add(nodeInfo2);
Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body };
var param = new PrmNetmapSnapshot();
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new Context
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Version
};
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
var result = await GetClient().GetNetmapSnapshotAsync(param);
var validTimeoutTo = DateTime.UtcNow.AddSeconds(20);
Assert.NotNull(result);
Assert.Equal(99u, result.Epoch);
Assert.Equal(2, result.NodeInfoCollection.Count);
var node1 = result.NodeInfoCollection[0];
Assert.Equal(NodeState.Online, node1.State);
Assert.Equal(2, node1.Addresses.Count);
Assert.Equal("address1", node1.Addresses.ElementAt(0));
Assert.Equal("address2", node1.Addresses.ElementAt(1));
Assert.Equal(2, node1.Attributes.Count);
Assert.Equal("key1", node1.Attributes.ElementAt(0).Key);
Assert.Equal("value1", node1.Attributes.ElementAt(0).Value);
Assert.Equal("key2", node1.Attributes.ElementAt(1).Key);
Assert.Equal("value2", node1.Attributes.ElementAt(1).Value);
var node2 = result.NodeInfoCollection[1];
Assert.Equal(NodeState.Offline, node2.State);
Assert.Single(node2.Addresses);
Assert.Equal("address3", node2.Addresses.ElementAt(0));
Assert.Single(node2.Attributes);
Assert.Equal("key3", node2.Attributes.ElementAt(0).Key);
Assert.Equal("value3", node2.Attributes.ElementAt(0).Value);
if (useContext)
{
Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
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(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken);
Assert.NotNull(Mocker.DateTime);
Assert.True(Mocker.DateTime.Value >= validTimeoutFrom);
Assert.True(Mocker.DateTime.Value <= validTimeoutTo);
}
else
{
Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NodeInfoTest(bool useContext)
{
var body = new LocalNodeInfoResponse.Types.Body
{
NodeInfo = new NodeInfo()
{
State = NodeInfo.Types.State.Online,
PublicKey = ByteString.CopyFrom([1, 2, 3])
},
Version = new Refs.Version { Major = 2, Minor = 12 }
};
body.NodeInfo.Addresses.Add("address1");
body.NodeInfo.Addresses.Add("address2");
body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"});
body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" });
Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body };
var param = new PrmNodeInfo();
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new Context
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Version
};
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
var result = await GetClient().GetNodeInfoAsync(param);
var validTimeoutTo = DateTime.UtcNow.AddSeconds(20);
Assert.NotNull(result);
Assert.Equal(NodeState.Online, result.State);
Assert.Equal(2, result.Addresses.Count);
Assert.Equal("address1", result.Addresses.ElementAt(0));
Assert.Equal("address2", result.Addresses.ElementAt(1));
Assert.Equal(2, result.Attributes.Count);
Assert.Equal("value1", result.Attributes["key1"]);
Assert.Equal("value2", result.Attributes["key2"]);
if (useContext)
{
Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders);
Assert.Equal(param.XHeaders.Keys[0], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Key);
Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Value);
Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken);
Assert.NotNull(Mocker.DateTime);
Assert.True(Mocker.DateTime.Value >= validTimeoutFrom);
Assert.True(Mocker.DateTime.Value <= validTimeoutTo);
}
else
{
Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
}
}

View file

@ -19,7 +19,7 @@ public abstract class ObjectTestsBase
{
protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected IOptions<ClientSettings> Settings { get; set; }
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
protected ContainerId ContainerId { get; set; }
protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key);
@ -31,7 +31,7 @@ public abstract class ObjectTestsBase
{
var ecdsaKey = key.LoadWif();
Settings = Options.Create(new ClientSettings
Settings = Options.Create(new SingleOwnerClientSettings
{
Key = key,
Host = "http://localhost:8080"
@ -71,14 +71,17 @@ public class ObjectTest : ObjectTestsBase
public async void GetObjectTest()
{
var client = GetClient();
var objectId = client.CalculateObjectId(Mocker.ObjectHeader!);
var context = new Context
{
Timeout = TimeSpan.FromSeconds(2)
};
var ecdsaKey = key.LoadWif();
var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = context });
var ctx = new Context {
Key = ecdsaKey,
OwnerId = OwnerId.FromKey(ecdsaKey),
Version = new ModelsV2.Version(2, 13) };
var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx);
var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx });
Assert.NotNull(result);

View file

@ -0,0 +1,127 @@
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap;
using FrostFS.SDK.ClientV2.Parameters;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2.Netmap;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
namespace FrostFS.SDK.Tests;
public abstract class SessionTestsBase
{
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected IOptions<SingleOwnerClientSettings> Settings { get; set; }
protected ECDsa ECDsaKey { get; set; }
protected OwnerId 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 = OwnerId.FromKey(ECDsaKey);
Mocker = new SessionMocker(this.key)
{
PlacementPolicy = new PlacementPolicy(true, new Replica(1)),
Version = new ModelsV2.Version(2, 13)
};
}
protected IFrostFSClient GetClient()
{
return ClientV2.Client.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
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void CreateSessionTest(bool useContext)
{
var exp = 100u;
var param = new PrmSessionCreate(exp);
if (useContext)
{
param.XHeaders.Add("headerKey1", "headerValue1");
param.Context = new Context
{
CancellationToken = Mocker.CancellationTokenSource.Token,
Timeout = TimeSpan.FromSeconds(20),
OwnerId = OwnerId,
Key = ECDsaKey,
Version = Mocker.Version
};
}
var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20);
var result = await GetClient().CreateSessionAsync(param);
var validTimeoutTo = DateTime.UtcNow.AddSeconds(20);
Assert.NotNull(result);
Assert.NotNull(result.Token);
var session = new Session.SessionToken().Deserialize(result.Token);
Assert.Equal(Mocker.SessionId, session.Body.Id);
Assert.Equal(Mocker.SessionKey, session.Body.SessionKey);
Assert.Equal(OwnerId.ToMessage(), session.Body.OwnerId);
Assert.Equal(exp, session.Body.Lifetime.Exp);
Assert.Equal(exp, session.Body.Lifetime.Iat);
Assert.Equal(exp, session.Body.Lifetime.Nbf);
Assert.Null(session.Body.Container);
Assert.NotNull(Mocker.CreateSessionRequest);
Assert.Equal(OwnerId.ToMessage(), Mocker.CreateSessionRequest.Body.OwnerId);
Assert.Equal(exp, Mocker.CreateSessionRequest.Body.Expiration);
Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader);
Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version);
Assert.Null(Mocker.Metadata);
if (useContext)
{
Assert.Single(Mocker.CreateSessionRequest.MetaHeader.XHeaders);
Assert.Equal(param.XHeaders.Keys[0], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Key);
Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Value);
Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken);
Assert.NotNull(Mocker.DateTime);
Assert.True(Mocker.DateTime.Value >= validTimeoutFrom);
Assert.True(Mocker.DateTime.Value <= validTimeoutTo);
Assert.True(validTimeoutTo.Ticks >= Mocker.DateTime.Value.Ticks);
}
else
{
Assert.Empty(Mocker.CreateSessionRequest.MetaHeader.XHeaders);
Assert.Null(Mocker.DateTime);
}
}
}

View file

@ -13,21 +13,46 @@ using System.Diagnostics;
using static FrostFS.Session.SessionToken.Types.Body;
using FrostFS.SDK.ClientV2.Parameters;
using FrostFS.SDK.Tests;
namespace FrostFS.SDK.SmokeTests;
public class SmokeTests
public abstract class SmokeTestsBase
{
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected readonly string url = "http://172.23.32.4:8080";
protected ECDsa Key { get; }
protected OwnerId OwnerId { get; }
protected ModelsV2.Version Version { get; }
protected Context Ctx { get; }
protected SmokeTestsBase()
{
Key = key.LoadWif();
OwnerId = OwnerId.FromKey(Key);
Version = new ModelsV2.Version(2, 13);
Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version };
}
}
public class SmokeTests : SmokeTestsBase
{
private static readonly PrmWait lightWait = new (100, 1);
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
private readonly string url = "http://172.23.32.4:8080";
[Fact]
public async void NetworkMapTest()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NetworkMapTest(bool isSingleOnwerClient)
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url));
var result = await client.GetNetmapSnapshotAsync();
PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new () { Context = Ctx };
var result = await client.GetNetmapSnapshotAsync(prm);
Assert.True(result.Epoch > 0);
Assert.Single(result.NodeInfoCollection);
@ -41,12 +66,17 @@ public class SmokeTests
Assert.Equal(9, item.Attributes.Count);
}
[Fact]
public async void NodeInfoTest()
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
var result = await client.GetNodeInfoAsync();
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NodeInfoTest(bool isSingleOnwerClient)
{
using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url));
PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx };
var result = await client.GetNodeInfoAsync(prm);
Assert.Equal(2, result.Version.Major);
Assert.Equal(13, result.Version.Minor);
@ -64,25 +94,25 @@ public class SmokeTests
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
};
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
var result = await client.GetNodeInfoAsync();
}
[Fact]
public async void GetSessionTest()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void GetSessionTest(bool isSingleOnwerClient)
{
var ecdsaKey = this.key.LoadWif();
using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url));
using var client = Client.GetInstance(GetOptions(this.key, this.url));
PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx };
var token = await client.CreateSessionAsync(new PrmSessionCreate(100));
var token = await client.CreateSessionAsync(prm);
var session = new Session.SessionToken().Deserialize(token.Token);
var owner = OwnerId.FromKey(ecdsaKey);
var ownerHash = Base58.Decode(owner.Value);
var ownerHash = Base58.Decode(OwnerId.Value);
Assert.NotNull(session);
Assert.Null(session.Body.Container);
@ -97,7 +127,7 @@ public class SmokeTests
[Fact]
public async void CreateObjectWithSessionToken()
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
await Cleanup(client);
@ -144,7 +174,7 @@ public class SmokeTests
[Fact]
public async void FilterTest()
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
await Cleanup(client);
@ -230,7 +260,7 @@ public class SmokeTests
[InlineData(6 * 1024 * 1024 + 100)]
public async void SimpleScenarioTest(int objectSize)
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
await Cleanup(client);
@ -253,7 +283,7 @@ public class SmokeTests
var containerId = await client.CreateContainerAsync(createContainerParam);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx));
var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
Assert.NotNull(container);
Assert.True(callbackInvoked);
@ -318,7 +348,7 @@ public class SmokeTests
[InlineData(6 * 1024 * 1024 + 100)]
public async void SimpleScenarioWithSessionTest(int objectSize)
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue));
@ -338,7 +368,7 @@ public class SmokeTests
var containerId = await client.CreateContainerAsync(createContainerParam);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx));
var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
Assert.NotNull(container);
var bytes = GetRandomBytes(objectSize);
@ -406,7 +436,7 @@ public class SmokeTests
[InlineData(200)]
public async void ClientCutScenarioTest(int objectSize)
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url));
await Cleanup(client);
@ -417,13 +447,13 @@ public class SmokeTests
var containerId = await client.CreateContainerAsync(createContainerParam);
var context = new Context
var ctx = new Context
{
Timeout = TimeSpan.FromSeconds(10),
Interceptors = new([new MetricsInterceptor()])
};
var container = await client.GetContainerAsync(new PrmContainerGet(containerId, context));
var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx });
Assert.NotNull(container);
@ -499,11 +529,19 @@ public class SmokeTests
return bytes;
}
private static IOptions<ClientSettings> GetOptions(string key, string url)
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
{
Key = key,
Host = url
});
}