[#21] Client: Allows multinenant client

Using one client for several owners

Signed-off-by: Pavel Gross <p.gross@yando.com>
This commit is contained in:
Pavel Gross 2024-08-12 10:46:59 +03:00
parent 18126ea763
commit 2a28806ace
30 changed files with 349 additions and 281 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,18 +62,18 @@ public class Client : IFrostFSClient
}
private Client(
IOptions<ClientSettings> settings,
GrpcChannelOptions? channelOptions,
ContainerService.ContainerServiceClient containerService,
NetmapService.NetmapServiceClient netmapService,
SessionService.SessionServiceClient sessionService,
ObjectService.ObjectServiceClient objectService)
IOptions<SingleOwnerClientSettings> settings,
GrpcChannelOptions? channelOptions,
ContainerService.ContainerServiceClient containerService,
NetmapService.NetmapServiceClient netmapService,
SessionService.SessionServiceClient sessionService,
ObjectService.ObjectServiceClient objectService)
{
var ecdsaKey = settings.Value.Key.LoadWif();
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; }
@ -20,5 +33,18 @@ 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;
@ -12,12 +11,6 @@ public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = n
/// </summary>
/// <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
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
public sealed class PrmNetmapSnapshot() : PrmBase
{
}

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
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
public sealed class PrmNetworkSettings() : PrmBase
{
}

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
{
/// <summary>
/// FrostFS request X-Headers
/// </summary>
public NameValueCollection XHeaders { get; set; } = [];
/// <inheritdoc />
public Context? Context { get; set; } = context;
public sealed class PrmNodeInfo() : PrmBase
{
}

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,10 +1,10 @@
using FrostFS.SDK.ModelsV2;
using System.Collections.Specialized;
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.
@ -30,21 +30,13 @@ public sealed class PrmObjectPut : IContext, ISessionToken
/// Overrides default size of the buffer for stream transferring.
/// </summary>
/// <value>Size of the buffer</value>
public int BufferMaxSize { get; set; }
public int BufferMaxSize { get; set; }
/// <summary>
/// Allows to define a buffer for chunks to manage by the memory allocation and releasing.
/// </summary>
public byte[]? CustomBuffer { get; set; }
/// <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,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
@ -16,15 +15,7 @@ public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilte
/// </summary>
/// <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;
}
@ -331,7 +331,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
while (objectLimitSize == 0 || sentBytes < objectLimitSize)
{
// send chanks limited to default or user's settings
// send chunks limited to default or user's settings
var bufferSize = objectLimitSize > 0 ?
(int)Math.Min(objectLimitSize - sentBytes, chunkSize)
: chunkSize;
@ -350,8 +350,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount)
}
};
chunkRequest.Sign(Context.Key.ECDsaKey);
chunkRequest.Sign(ctx.Key);
await stream.Write(chunkRequest);
}
@ -374,14 +374,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() };
@ -402,12 +402,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,25 +1,26 @@
using FrostFS.SDK.ModelsV2;
using Google.Protobuf;
using Grpc.Net.Client;
using System;
using System.Security.Cryptography;
using FrostFS.SDK.Cryptography;
using System.Buffers;
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
{
private ArrayPool<byte> _arrayPool;
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;
/// <summary>
/// Custom pool is used for predefined sizes of buffers like grpc chunk

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

@ -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

@ -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

@ -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()
{
using var client = Client.GetInstance(GetOptions(this.key, this.url));
var result = await client.GetNetmapSnapshotAsync();
[Theory]
[InlineData(false)]
[InlineData(true)]
public async void NetworkMapTest(bool isSingleOnwerClient)
{
using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url));
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
});
}