[#17] Client: Add extra parameter #18
42 changed files with 1054 additions and 386 deletions
|
@ -2,6 +2,7 @@
|
||||||
using FrostFS.Netmap;
|
using FrostFS.Netmap;
|
||||||
using FrostFS.Object;
|
using FrostFS.Object;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
|
@ -96,7 +97,8 @@ public class Client : IFrostFSClient
|
||||||
channel: channel,
|
channel: channel,
|
||||||
version: new Version(2, 13));
|
version: new Version(2, 13));
|
||||||
|
|
||||||
CheckFrostFsVersionSupport();
|
// TODO: define timeout logic
|
||||||
|
CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20)});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -115,105 +117,106 @@ public class Client : IFrostFSClient
|
||||||
}
|
}
|
||||||
|
|
||||||
#region ContainerImplementation
|
#region ContainerImplementation
|
||||||
public Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? ctx = null)
|
public Task<ModelsV2.Container> GetContainerAsync(PrmGetContainer args)
|
||||||
{
|
{
|
||||||
var service = GetContainerService(ref ctx);
|
var service = GetContainerService(args);
|
||||||
return service.GetContainerAsync(containerId, ctx!);
|
return service.GetContainerAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ContainerId> ListContainersAsync(Context? ctx = default)
|
public IAsyncEnumerable<ContainerId> ListContainersAsync(PrmListContainer? args = null)
|
||||||
{
|
{
|
||||||
var service = GetContainerService(ref ctx);
|
args = args ?? new PrmListContainer();
|
||||||
return service.ListContainersAsync(ctx!);
|
var service = GetContainerService(args);
|
||||||
|
return service.ListContainersAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? ctx = null)
|
public Task<ContainerId> CreateContainerAsync(PrmCreateContainer args)
|
||||||
{
|
{
|
||||||
var service = GetContainerService(ref ctx);
|
var service = GetContainerService(args);
|
||||||
return service.CreateContainerAsync(container, ctx!);
|
return service.CreateContainerAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default)
|
public Task DeleteContainerAsync(PrmDeleteContainer args)
|
||||||
{
|
{
|
||||||
var service = GetContainerService(ref ctx);
|
var service = GetContainerService(args);
|
||||||
return service.DeleteContainerAsync(containerId, ctx!);
|
return service.DeleteContainerAsync(args);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region NetworkImplementation
|
#region NetworkImplementation
|
||||||
public Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
|
public Task<NetmapSnapshot> GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args)
|
||||||
{
|
{
|
||||||
var service = GetNetmapService(ref ctx);
|
args ??= new PrmGetNetmapSnapshot();
|
||||||
return service.GetNetmapSnapshotAsync(ctx!);
|
var service = GetNetmapService(args);
|
||||||
|
return service.GetNetmapSnapshotAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
|
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(PrmGetNodeInfo? args)
|
||||||
{
|
{
|
||||||
var service = GetNetmapService(ref ctx);
|
args ??= new PrmGetNodeInfo();
|
||||||
return service.GetLocalNodeInfoAsync(ctx!);
|
var service = GetNetmapService(args);
|
||||||
|
return service.GetLocalNodeInfoAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<NetworkSettings> GetNetworkSettingsAsync(Context? ctx = default)
|
public Task<NetworkSettings> GetNetworkSettingsAsync(PrmGetNetworkSettings? args)
|
||||||
{
|
{
|
||||||
var service = GetNetmapService(ref ctx);
|
args ??= new PrmGetNetworkSettings();
|
||||||
return service.GetNetworkSettingsAsync(ctx!);
|
var service = GetNetmapService(args);
|
||||||
|
return service.GetNetworkSettingsAsync(args.Context!);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ObjectImplementation
|
#region ObjectImplementation
|
||||||
public Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task<ObjectHeader> GetObjectHeadAsync(PrmGetObjectHead args)
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.GetObjectHeadAsync(containerId, objectId, ctx!);
|
return service.GetObjectHeadAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<FrostFsObject> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task<FrostFsObject> GetObjectAsync(PrmGetObject args)
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.GetObjectAsync(containerId, objectId, ctx!);
|
return service.GetObjectAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default)
|
public Task<ObjectId> PutObjectAsync(PrmPutObject args)
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.PutObjectAsync(putObjectParameters, ctx!);
|
return service.PutObjectAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ObjectId> PutSingleObjectAsync(FrostFsObject obj, Context? ctx = default)
|
public Task<ObjectId> PutSingleObjectAsync(PrmPutSingleObject args)
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.PutSingleObjectAsync(obj, ctx!);
|
return service.PutSingleObjectAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
|
public Task DeleteObjectAsync(PrmDeleteObject args)
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.DeleteObjectAsync(containerId, objectId, ctx!);
|
return service.DeleteObjectAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ObjectId> SearchObjectsAsync(
|
public IAsyncEnumerable<ObjectId> SearchObjectsAsync(PrmSearchObject args)
|
||||||
ContainerId containerId,
|
|
||||||
IEnumerable<ObjectFilter> filters,
|
|
||||||
Context? ctx = default)
|
|
||||||
{
|
{
|
||||||
var service = GetObjectService(ref ctx);
|
var service = GetObjectService(args);
|
||||||
return service.SearchObjectsAsync(containerId, filters, ctx!);
|
return service.SearchObjectsAsync(args);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SessionImplementation
|
#region SessionImplementation
|
||||||
public async Task<ModelsV2.SessionToken> CreateSessionAsync(ulong expiration, Context? ctx = null)
|
public async Task<ModelsV2.SessionToken> CreateSessionAsync(PrmCreateSession args)
|
||||||
{
|
{
|
||||||
var session = await CreateSessionInternalAsync(expiration, ctx);
|
var session = await CreateSessionInternalAsync(args);
|
||||||
var token = session.Serialize();
|
var token = session.Serialize();
|
||||||
|
|
||||||
return new ModelsV2.SessionToken([], token);
|
return new ModelsV2.SessionToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Session.SessionToken> CreateSessionInternalAsync(ulong expiration, Context? ctx = null)
|
internal Task<Session.SessionToken> CreateSessionInternalAsync(PrmCreateSession args)
|
||||||
{
|
{
|
||||||
var service = GetSessionService(ref ctx);
|
var service = GetSessionService(args);
|
||||||
return service.CreateSessionAsync(expiration, ctx!);
|
return service.CreateSessionAsync(args);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -226,8 +229,9 @@ public class Client : IFrostFSClient
|
||||||
|
|
||||||
private async void CheckFrostFsVersionSupport(Context? ctx = default)
|
private async void CheckFrostFsVersionSupport(Context? ctx = default)
|
||||||
{
|
{
|
||||||
var service = GetNetmapService(ref ctx);
|
var args = new PrmGetNodeInfo(ctx);
|
||||||
var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!);
|
var service = GetNetmapService(args);
|
||||||
|
var localNodeInfo = await service.GetLocalNodeInfoAsync(args);
|
||||||
|
|
||||||
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
|
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
|
||||||
{
|
{
|
||||||
|
@ -237,24 +241,24 @@ public class Client : IFrostFSClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallInvoker? SetupEnvironment(ref Context? ctx)
|
private CallInvoker? SetupEnvironment(IContext ctx)
|
||||||
{
|
{
|
||||||
if (isDisposed)
|
if (isDisposed)
|
||||||
throw new Exception("Client is disposed.");
|
throw new Exception("Client is disposed.");
|
||||||
|
|
||||||
ctx ??= new Context();
|
ctx.Context ??= new Context();
|
||||||
|
|
||||||
CallInvoker? callInvoker = null;
|
CallInvoker? callInvoker = null;
|
||||||
if (ctx.Interceptors != null && ctx.Interceptors.Count > 0)
|
if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var interceptor in ctx.Interceptors)
|
foreach (var interceptor in ctx.Context.Interceptors)
|
||||||
{
|
{
|
||||||
callInvoker = AddInvoker(callInvoker, interceptor);
|
callInvoker = AddInvoker(callInvoker, interceptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Callback != null)
|
if (ctx.Context.Callback != null)
|
||||||
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Callback));
|
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
|
||||||
|
|
||||||
return callInvoker;
|
return callInvoker;
|
||||||
|
|
||||||
|
@ -269,9 +273,9 @@ public class Client : IFrostFSClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NetmapServiceProvider GetNetmapService(ref Context? ctx)
|
private NetmapServiceProvider GetNetmapService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ref ctx);
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
var client = NetmapServiceClient ?? (callInvoker != null
|
var client = NetmapServiceClient ?? (callInvoker != null
|
||||||
? new NetmapService.NetmapServiceClient(callInvoker)
|
? new NetmapService.NetmapServiceClient(callInvoker)
|
||||||
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
||||||
|
@ -279,9 +283,9 @@ public class Client : IFrostFSClient
|
||||||
return new NetmapServiceProvider(client, ClientCtx);
|
return new NetmapServiceProvider(client, ClientCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionServiceProvider GetSessionService(ref Context? ctx)
|
private SessionServiceProvider GetSessionService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ref ctx);
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
var client = SessionServiceClient ?? (callInvoker != null
|
var client = SessionServiceClient ?? (callInvoker != null
|
||||||
? new SessionService.SessionServiceClient(callInvoker)
|
? new SessionService.SessionServiceClient(callInvoker)
|
||||||
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
||||||
|
@ -289,9 +293,9 @@ public class Client : IFrostFSClient
|
||||||
return new SessionServiceProvider(client, ClientCtx);
|
return new SessionServiceProvider(client, ClientCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContainerServiceProvider GetContainerService(ref Context? ctx)
|
private ContainerServiceProvider GetContainerService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ref ctx);
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
var client = ContainerServiceClient ?? (callInvoker != null
|
var client = ContainerServiceClient ?? (callInvoker != null
|
||||||
? new ContainerService.ContainerServiceClient(callInvoker)
|
? new ContainerService.ContainerServiceClient(callInvoker)
|
||||||
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
||||||
|
@ -299,9 +303,9 @@ public class Client : IFrostFSClient
|
||||||
return new ContainerServiceProvider(client, ClientCtx);
|
return new ContainerServiceProvider(client, ClientCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectServiceProvider GetObjectService(ref Context? ctx)
|
private ObjectServiceProvider GetObjectService(IContext ctx)
|
||||||
{
|
{
|
||||||
var callInvoker = SetupEnvironment(ref ctx);
|
var callInvoker = SetupEnvironment(ctx);
|
||||||
var client = ObjectServiceClient ?? (callInvoker != null
|
var client = ObjectServiceClient ?? (callInvoker != null
|
||||||
? new ObjectService.ObjectServiceClient(callInvoker)
|
? new ObjectService.ObjectServiceClient(callInvoker)
|
||||||
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
||||||
|
|
12
src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs
Normal file
12
src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
public class ResponseException : Exception
|
||||||
|
{
|
||||||
|
public Status Status { get; set; }
|
||||||
|
|
||||||
|
public ResponseException(Status status) : base()
|
||||||
|
{
|
||||||
|
Status = status;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +1,50 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2.Interfaces;
|
namespace FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
|
||||||
public interface IFrostFSClient : IDisposable
|
public interface IFrostFSClient : IDisposable
|
||||||
{
|
{
|
||||||
#region Network
|
#region Network
|
||||||
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
|
Task<NetmapSnapshot> GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args = null);
|
||||||
|
|
||||||
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
|
Task<NodeInfo> GetNodeInfoAsync(PrmGetNodeInfo? args = null);
|
||||||
|
|
||||||
Task<NetworkSettings> GetNetworkSettingsAsync(Context? context = default);
|
Task<NetworkSettings> GetNetworkSettingsAsync(PrmGetNetworkSettings? args = null);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Session
|
#region Session
|
||||||
Task<SessionToken> CreateSessionAsync(ulong expiration, Context? context = default);
|
Task<SessionToken> CreateSessionAsync(PrmCreateSession args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Container
|
#region Container
|
||||||
Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? context = default);
|
Task<ModelsV2.Container> GetContainerAsync(PrmGetContainer args);
|
||||||
|
|
||||||
IAsyncEnumerable<ContainerId> ListContainersAsync(Context? context = default);
|
IAsyncEnumerable<ContainerId> ListContainersAsync(PrmListContainer? args = null);
|
||||||
|
|
||||||
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? context = default);
|
Task<ContainerId> CreateContainerAsync(PrmCreateContainer args);
|
||||||
|
|
||||||
Task DeleteContainerAsync(ContainerId containerId, Context? context = default);
|
Task DeleteContainerAsync(PrmDeleteContainer args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Object
|
#region Object
|
||||||
Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
Task<ObjectHeader> GetObjectHeadAsync(PrmGetObjectHead args);
|
||||||
|
|
||||||
Task<FrostFsObject> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
Task<FrostFsObject> GetObjectAsync(PrmGetObject args);
|
||||||
|
|
||||||
Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default);
|
Task<ObjectId> PutObjectAsync(PrmPutObject putObjectParameters);
|
||||||
|
|
||||||
Task<ObjectId> PutSingleObjectAsync(FrostFsObject obj, Context? context = default);
|
Task<ObjectId> PutSingleObjectAsync(PrmPutSingleObject args);
|
||||||
|
|
||||||
Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
|
Task DeleteObjectAsync(PrmDeleteObject args);
|
||||||
|
|
||||||
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, IEnumerable<ObjectFilter> filters, Context? context = default);
|
IAsyncEnumerable<ObjectId> SearchObjectsAsync(PrmSearchObject args);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tools
|
#region Tools
|
||||||
ObjectId CalculateObjectId(ObjectHeader header);
|
ObjectId CalculateObjectId(ObjectHeader header);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
|
||||||
public static class ObjectMapper
|
internal static class ObjectMapper
|
||||||
{
|
{
|
||||||
public static FrostFsObject ToModel(this Object.Object obj)
|
internal static FrostFsObject ToModel(this Object.Object obj)
|
||||||
{
|
{
|
||||||
return new FrostFsObject(
|
return new FrostFsObject(obj.Header.ToModel())
|
||||||
ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()),
|
{
|
||||||
obj.Header.ToModel(),
|
ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray())
|
||||||
obj.Payload.ToByteArray());
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public static class SessionMapper
|
public static class SessionMapper
|
||||||
{
|
{
|
||||||
internal static byte[] Serialize(this Session.SessionToken token)
|
public static byte[] Serialize(this Session.SessionToken token)
|
||||||
{
|
{
|
||||||
byte[] bytes = new byte[token.CalculateSize()];
|
byte[] bytes = new byte[token.CalculateSize()];
|
||||||
CodedOutputStream stream = new(bytes);
|
CodedOutputStream stream = new(bytes);
|
||||||
|
@ -13,11 +13,9 @@ public static class SessionMapper
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes)
|
public static Session.SessionToken Deserialize(this Session.SessionToken token, byte[] bytes)
|
||||||
{
|
{
|
||||||
Session.SessionToken token = new();
|
|
||||||
token.MergeFrom(bytes);
|
token.MergeFrom(bytes);
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
11
src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
Normal file
11
src/FrostFS.SDK.ClientV2/Parameters/IContext.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public interface IContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The method call can be extended with additional behavior like canceling by timeout or user's request,
|
||||||
|
/// callbacks, interceptors.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Additional parameters for calling the method</value>
|
||||||
|
Context? Context { get; set; }
|
||||||
|
}
|
14
src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs
Normal file
14
src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public interface ISessionToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Object represents token of the FrostFS Object session. A session is opened between any two sides of the
|
||||||
|
/// system, and implements a mechanism for transferring the power of attorney of actions to another network
|
||||||
|
/// member. The session has a limited validity period, and applies to a strictly defined set of operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Instance of the session obtained from the server</value>
|
||||||
|
SessionToken? SessionToken { get; set; }
|
||||||
|
}
|
22
src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs
Normal file
22
src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmCreateContainer(ModelsV2.Container container) : IContext
|
||||||
|
{
|
||||||
|
public ModelsV2.Container Container { get; set; } = container;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Since the container becomes available with some delay, it needs to poll the container status
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Rules for polling the result</value>
|
||||||
|
public PrmWait? WaitParams { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; }
|
||||||
|
}
|
16
src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs
Normal file
16
src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmCreateSession(ulong expiration, Context? context = default) : IContext
|
||||||
|
{
|
||||||
|
public ulong Expiration { get; set; } = expiration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = context;
|
||||||
|
}
|
23
src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs
Normal file
23
src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmDeleteContainer(ContainerId containerId, Context? ctx = null) : IContext
|
||||||
|
{
|
||||||
|
public ContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Since the container is removed with some delay, it needs to poll the container status
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Rules for polling the result</value>
|
||||||
|
public PrmWait? WaitParams { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = ctx;
|
||||||
|
}
|
21
src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs
Normal file
21
src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmDeleteObject(ContainerId containerId, ObjectId objectId) : IContext, 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; }
|
||||||
|
}
|
17
src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs
Normal file
17
src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetContainer(ContainerId containerId, Context? ctx = null) : IContext
|
||||||
|
{
|
||||||
|
public ContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = ctx;
|
||||||
|
}
|
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs
Normal file
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetNetmapSnapshot(Context? context = default) : IContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = context;
|
||||||
|
}
|
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs
Normal file
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetNetworkSettings(Context? context = default) : IContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = context;
|
||||||
|
}
|
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs
Normal file
14
src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetNodeInfo(Context? context = default) : IContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; } = context;
|
||||||
|
}
|
21
src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs
Normal file
21
src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetObject(ContainerId containerId, ObjectId objectId) : IContext, 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; }
|
||||||
|
}
|
21
src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs
Normal file
21
src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmGetObjectHead(ContainerId containerId, ObjectId objectId) : IContext, 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; }
|
||||||
|
}
|
16
src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs
Normal file
16
src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmListContainer() : IContext
|
||||||
|
{
|
||||||
|
public string SessionToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; }
|
||||||
|
}
|
44
src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs
Normal file
44
src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmPutObject : IContext, ISessionToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Need to provide values like <c>ContainerId</c> and <c>ObjectType</c> to create and object.
|
||||||
|
/// Optional parameters ike <c>Attributes</c> can be provided as well.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Header with required parameters to create an object</value>
|
||||||
|
public ObjectHeader? Header { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A stream with source data
|
||||||
|
/// </summary>
|
||||||
|
public Stream? Payload { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object size is limited. In the data exceeds the limit, the object will be splitted.
|
||||||
|
/// If the parameter is <c>true</c>, the client side cut is applied. Otherwise, the data is transferred
|
||||||
|
/// as a stream and will be cut on server side.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Is client cut is applied</value>
|
||||||
|
public bool ClientCut { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides default size of the buffer for stream transferring.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Size of the buffer</value>
|
||||||
|
public int BufferMaxSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public NameValueCollection XHeaders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Context? Context { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public SessionToken? SessionToken { get; set; }
|
||||||
|
}
|
19
src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs
Normal file
19
src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmPutSingleObject(FrostFsObject frostFsObject, Context? context = null) : IContext, 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; }
|
||||||
|
}
|
26
src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs
Normal file
26
src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public sealed class PrmSearchObject(ContainerId containerId, params ObjectFilter[] filters) : IContext, ISessionToken
|
||||||
|
{
|
||||||
|
public ContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the search criteria
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Collection of filters</value>
|
||||||
|
public IEnumerable<ObjectFilter> 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; }
|
||||||
|
}
|
23
src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs
Normal file
23
src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
public class PrmWait(TimeSpan timeout, TimeSpan pollInterval)
|
||||||
|
{
|
||||||
|
private static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(120);
|
||||||
|
private static TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
public PrmWait(int timeout, int interval) : this(TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(interval))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrmWait DefaultParams { get; } = new PrmWait(DefaultTimeout, DefaultPollInterval);
|
||||||
|
|
||||||
|
public TimeSpan Timeout { get; set; } = timeout.Ticks == 0 ? DefaultTimeout : timeout;
|
||||||
|
|
||||||
|
public TimeSpan PollInterval { get; set; } = pollInterval.Ticks == 0 ? DefaultPollInterval : pollInterval;
|
||||||
|
|
||||||
|
public DateTime GetDeadline()
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow.AddTicks(Timeout.Ticks);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,10 @@ using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using System;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
@ -19,28 +23,21 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
containerServiceClient = service;
|
containerServiceClient = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<ModelsV2.Container> GetContainerAsync(ContainerId cid, Context context)
|
internal async Task<ModelsV2.Container> GetContainerAsync(PrmGetContainer args)
|
||||||
{
|
{
|
||||||
var request = new GetRequest
|
GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders);
|
||||||
{
|
|
||||||
Body = new GetRequest.Types.Body
|
|
||||||
{
|
|
||||||
ContainerId = cid.ToGrpcMessage()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var response = await containerServiceClient.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
|
||||||
request.Sign(Context.Key);
|
|
||||||
|
|
||||||
var response = await containerServiceClient.GetAsync(request, null, context.Deadline, context.CancellationToken);
|
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
return response.Body.Container.ToModel();
|
return response.Body.Container.ToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async IAsyncEnumerable<ContainerId> ListContainersAsync(Context ctx)
|
internal async IAsyncEnumerable<ContainerId> ListContainersAsync(PrmListContainer args)
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
|
|
||||||
var request = new ListRequest
|
var request = new ListRequest
|
||||||
{
|
{
|
||||||
Body = new ListRequest.Types.Body
|
Body = new ListRequest.Types.Body
|
||||||
|
@ -49,7 +46,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
@ -62,9 +59,10 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context ctx)
|
internal async Task<ContainerId> CreateContainerAsync(PrmCreateContainer args)
|
||||||
{
|
{
|
||||||
var grpcContainer = container.ToGrpcMessage();
|
var ctx = args.Context!;
|
||||||
|
var grpcContainer = args.Container.ToGrpcMessage();
|
||||||
grpcContainer.OwnerId = Context.Owner.ToGrpcMessage();
|
grpcContainer.OwnerId = Context.Owner.ToGrpcMessage();
|
||||||
grpcContainer.Version = Context.Version.ToGrpcMessage();
|
grpcContainer.Version = Context.Version.ToGrpcMessage();
|
||||||
|
|
||||||
|
@ -73,37 +71,113 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
Body = new PutRequest.Types.Body
|
Body = new PutRequest.Types.Body
|
||||||
{
|
{
|
||||||
Container = grpcContainer,
|
Container = grpcContainer,
|
||||||
Signature = Context.Key.SignRFC6979(grpcContainer),
|
Signature = Context.Key.SignRFC6979(grpcContainer)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
request.AddMetaHeader();
|
|
||||||
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx);
|
||||||
|
|
||||||
return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray()));
|
return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task DeleteContainerAsync(ContainerId cid, Context ctx)
|
internal async Task DeleteContainerAsync(PrmDeleteContainer args)
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
var request = new DeleteRequest
|
var request = new DeleteRequest
|
||||||
{
|
{
|
||||||
Body = new DeleteRequest.Types.Body
|
Body = new DeleteRequest.Types.Body
|
||||||
{
|
{
|
||||||
ContainerId = cid.ToGrpcMessage(),
|
ContainerId = args.ContainerId.ToGrpcMessage(),
|
||||||
Signature = Context.Key.SignRFC6979(cid.ToGrpcMessage().Value)
|
Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
|
await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders)
|
||||||
|
{
|
||||||
|
var request = new GetRequest
|
||||||
|
{
|
||||||
|
Body = new GetRequest.Types.Body
|
||||||
|
{
|
||||||
|
ContainerId = id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.AddMetaHeader(xHeaders);
|
||||||
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum WaitExpects
|
||||||
|
{
|
||||||
|
Exists,
|
||||||
|
Removed
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx)
|
||||||
|
{
|
||||||
|
var request = GetContainerRequest(id, null);
|
||||||
|
|
||||||
|
async Task action()
|
||||||
|
{
|
||||||
|
var response = await containerServiceClient.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
Verifier.CheckResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
await WaitFor(action, expect, waitParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WaitFor(
|
||||||
|
Func<Task> action,
|
||||||
|
WaitExpects expect,
|
||||||
|
PrmWait? waitParams)
|
||||||
|
{
|
||||||
|
waitParams ??= PrmWait.DefaultParams;
|
||||||
|
var deadLine = waitParams.GetDeadline();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
|
||||||
|
if (expect == WaitExpects.Exists)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Task.Delay(waitParams.PollInterval);
|
||||||
|
}
|
||||||
|
catch (ResponseException ex)
|
||||||
|
{
|
||||||
|
if (DateTime.UtcNow >= deadLine)
|
||||||
|
throw new TimeoutException();
|
||||||
|
|
||||||
|
if (ex.Status.Code != ModelsV2.Enums.StatusCode.ContainerNotFound)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
if (expect == WaitExpects.Removed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Task.Delay(waitParams.PollInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo;
|
||||||
|
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
using static FrostFS.Netmap.NetworkConfig.Types;
|
using static FrostFS.Netmap.NetworkConfig.Types;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
@ -39,14 +40,15 @@ internal class NetmapServiceProvider : ContextAccessor
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<NodeInfo> GetLocalNodeInfoAsync(Context ctx)
|
internal async Task<NodeInfo> GetLocalNodeInfoAsync(PrmGetNodeInfo args)
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
var request = new LocalNodeInfoRequest
|
var request = new LocalNodeInfoRequest
|
||||||
{
|
{
|
||||||
Body = new LocalNodeInfoRequest.Types.Body { }
|
Body = new LocalNodeInfoRequest.Types.Body { }
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
@ -58,12 +60,9 @@ internal class NetmapServiceProvider : ContextAccessor
|
||||||
|
|
||||||
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(Context ctx)
|
internal async Task<NetworkInfoResponse> GetNetworkInfoAsync(Context ctx)
|
||||||
{
|
{
|
||||||
var request = new NetworkInfoRequest
|
var request = new NetworkInfoRequest();
|
||||||
{
|
|
||||||
Body = new NetworkInfoRequest.Types.Body { }
|
|
||||||
};
|
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader(null);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
@ -73,14 +72,13 @@ internal class NetmapServiceProvider : ContextAccessor
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context ctx)
|
internal async Task<NetmapSnapshot> GetNetmapSnapshotAsync(PrmGetNetmapSnapshot args)
|
||||||
{
|
{
|
||||||
var request = new NetmapSnapshotRequest
|
var ctx = args.Context!;
|
||||||
{
|
|
||||||
Body = new NetmapSnapshotRequest.Types.Body { }
|
|
||||||
};
|
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var request = new NetmapSnapshotRequest();
|
||||||
|
|
||||||
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
|
@ -12,6 +12,8 @@ using FrostFS.SDK.Cryptography;
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ClientV2.Extensions;
|
using FrostFS.SDK.ClientV2.Extensions;
|
||||||
|
using System.Threading;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
@ -19,21 +21,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
{
|
{
|
||||||
readonly ObjectTools tools = new(ctx);
|
readonly ObjectTools tools = new(ctx);
|
||||||
|
|
||||||
internal async Task<ObjectHeader> GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx)
|
internal async Task<ObjectHeader> GetObjectHeadAsync(PrmGetObjectHead args)
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
var request = new HeadRequest
|
var request = new HeadRequest
|
||||||
{
|
{
|
||||||
Body = new HeadRequest.Types.Body
|
Body = new HeadRequest.Types.Body
|
||||||
{
|
{
|
||||||
Address = new Address
|
Address = new Address
|
||||||
{
|
{
|
||||||
ContainerId = cid.ToGrpcMessage(),
|
ContainerId = args.ContainerId.ToGrpcMessage(),
|
||||||
ObjectId = oid.ToGrpcMessage()
|
ObjectId = args.ObjectId.ToGrpcMessage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
|
|
||||||
|
sessionToken.CreateObjectTokenContext(
|
||||||
|
request.Body.Address,
|
||||||
|
ObjectSessionContext.Types.Verb.Head,
|
||||||
|
Context.Key);
|
||||||
|
|
||||||
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
@ -43,9 +54,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
return response.Body.Header.Header.ToModel();
|
return response.Body.Header.Header.ToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<FrostFsObject> GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx)
|
internal async Task<FrostFsObject> GetObjectAsync(PrmGetObject args)
|
||||||
{
|
{
|
||||||
var sessionToken = await GetOrCreateSession(ctx);
|
var ctx = args.Context!;
|
||||||
|
|
||||||
var request = new GetRequest
|
var request = new GetRequest
|
||||||
{
|
{
|
||||||
|
@ -53,41 +64,49 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
{
|
{
|
||||||
Address = new Address
|
Address = new Address
|
||||||
{
|
{
|
||||||
ContainerId = cid.ToGrpcMessage(),
|
ContainerId = args.ContainerId.ToGrpcMessage(),
|
||||||
ObjectId = oid.ToGrpcMessage()
|
ObjectId = args.ObjectId.ToGrpcMessage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
request.AddObjectSessionToken(
|
|
||||||
sessionToken,
|
sessionToken.CreateObjectTokenContext(
|
||||||
cid.ToGrpcMessage(),
|
request.Body.Address,
|
||||||
oid.ToGrpcMessage(),
|
|
||||||
ObjectSessionContext.Types.Verb.Get,
|
ObjectSessionContext.Types.Verb.Get,
|
||||||
Context.Key
|
Context.Key);
|
||||||
);
|
|
||||||
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
return await GetObject(request, ctx);
|
return await GetObject(request, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx)
|
internal async Task DeleteObjectAsync(PrmDeleteObject args)
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
var request = new DeleteRequest
|
var request = new DeleteRequest
|
||||||
{
|
{
|
||||||
Body = new DeleteRequest.Types.Body
|
Body = new DeleteRequest.Types.Body
|
||||||
{
|
{
|
||||||
Address = new Address
|
Address = new Address
|
||||||
{
|
{
|
||||||
ContainerId = cid.ToGrpcMessage(),
|
ContainerId = args.ContainerId.ToGrpcMessage(),
|
||||||
ObjectId = oid.ToGrpcMessage()
|
ObjectId = args.ObjectId.ToGrpcMessage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
|
|
||||||
|
sessionToken.CreateObjectTokenContext(
|
||||||
|
request.Body.Address,
|
||||||
|
ObjectSessionContext.Types.Verb.Delete,
|
||||||
|
Context.Key);
|
||||||
|
|
||||||
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
@ -95,24 +114,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async IAsyncEnumerable<ObjectId> SearchObjectsAsync(
|
internal async IAsyncEnumerable<ObjectId> SearchObjectsAsync(PrmSearchObject args)
|
||||||
ContainerId cid,
|
|
||||||
IEnumerable<ObjectFilter> filters,
|
|
||||||
Context ctx)
|
|
||||||
{
|
{
|
||||||
|
var ctx = args.Context!;
|
||||||
var request = new SearchRequest
|
var request = new SearchRequest
|
||||||
{
|
{
|
||||||
Body = new SearchRequest.Types.Body
|
Body = new SearchRequest.Types.Body
|
||||||
{
|
{
|
||||||
ContainerId = cid.ToGrpcMessage(),
|
ContainerId = args.ContainerId.ToGrpcMessage(),
|
||||||
Filters = { },
|
Filters = { },
|
||||||
Version = 1 // TODO: clarify this param
|
Version = 1 // TODO: clarify this param
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.Body.Filters.AddRange(filters.Select(f => f.ToGrpcMessage()));
|
request.Body.Filters.AddRange(args.Filters.Select(f => f.ToGrpcMessage()));
|
||||||
|
|
||||||
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
|
|
||||||
|
sessionToken.CreateObjectTokenContext(
|
||||||
|
new Address { ContainerId = request.Body.ContainerId },
|
||||||
|
ObjectSessionContext.Types.Verb.Search,
|
||||||
|
Context.Key);
|
||||||
|
|
||||||
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
request.AddMetaHeader();
|
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var objectsIds = SearchObjects(request, ctx);
|
var objectsIds = SearchObjects(request, ctx);
|
||||||
|
@ -123,25 +148,24 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task<ObjectId> PutObjectAsync(PutObjectParameters parameters, Context ctx)
|
internal Task<ObjectId> PutObjectAsync(PrmPutObject args)
|
||||||
{
|
{
|
||||||
if (parameters.Header == null)
|
if (args.Header == null)
|
||||||
throw new ArgumentException("Value cannot be null", nameof(parameters.Header));
|
throw new ArgumentException("Value cannot be null", nameof(args.Header));
|
||||||
|
|
||||||
if (parameters.Payload == null)
|
if (args.Payload == null)
|
||||||
throw new ArgumentException("Value cannot be null", nameof(parameters.Payload));
|
throw new ArgumentException("Value cannot be null", nameof(args.Payload));
|
||||||
|
|
||||||
if (parameters.ClientCut)
|
if (args.ClientCut)
|
||||||
return PutClientCutObject(parameters, ctx);
|
return PutClientCutObject(args);
|
||||||
else
|
else
|
||||||
return PutStreamObject(parameters, ctx);
|
return PutStreamObject(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<ObjectId> PutSingleObjectAsync(FrostFsObject modelObject, Context ctx)
|
internal async Task<ObjectId> PutSingleObjectAsync(PrmPutSingleObject args)
|
||||||
{
|
{
|
||||||
var sessionToken = await GetOrCreateSession(ctx);
|
var ctx = args.Context!;
|
||||||
|
var grpcObject = tools.CreateObject(args.FrostFsObject);
|
||||||
var grpcObject = tools.CreateObject(modelObject);
|
|
||||||
|
|
||||||
var request = new PutSingleRequest
|
var request = new PutSingleRequest
|
||||||
{
|
{
|
||||||
|
@ -151,14 +175,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
request.AddObjectSessionToken(
|
|
||||||
sessionToken,
|
sessionToken.CreateObjectTokenContext(
|
||||||
grpcObject.Header.ContainerId,
|
new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId},
|
||||||
grpcObject.ObjectId,
|
|
||||||
ObjectSessionContext.Types.Verb.Put,
|
ObjectSessionContext.Types.Verb.Put,
|
||||||
Context.Key
|
Context.Key);
|
||||||
);
|
|
||||||
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
|
@ -169,17 +193,23 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ObjectId> PutClientCutObject(PutObjectParameters parameters, Context ctx)
|
static readonly AsyncLocal<Session.SessionToken> asyncLocalSession = new ();
|
||||||
|
|
||||||
|
private async Task<ObjectId> PutClientCutObject(PrmPutObject args)
|
||||||
{
|
{
|
||||||
var payloadStream = parameters.Payload!;
|
var ctx = args.Context!;
|
||||||
var header = parameters.Header!;
|
var tokenRaw = await GetOrCreateSession(args, ctx);
|
||||||
|
var token = new ModelsV2.SessionToken(tokenRaw.Serialize());
|
||||||
|
|
||||||
|
var payloadStream = args.Payload!;
|
||||||
|
var header = args.Header!;
|
||||||
|
|
||||||
ObjectId? objectId;
|
ObjectId? objectId;
|
||||||
List<ObjectId> sentObjectIds = [];
|
List<ObjectId> sentObjectIds = [];
|
||||||
|
|
||||||
FrostFsObject? currentObject;
|
FrostFsObject? currentObject;
|
||||||
|
|
||||||
var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx);
|
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmGetNetworkSettings(ctx));
|
||||||
|
|
||||||
var objectSize = (int)networkSettings.MaxObjectSize;
|
var objectSize = (int)networkSettings.MaxObjectSize;
|
||||||
|
|
||||||
|
@ -208,27 +238,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
|
|
||||||
split.Previous = sentObjectIds.LastOrDefault();
|
split.Previous = sentObjectIds.LastOrDefault();
|
||||||
|
|
||||||
largeObject.AppendBlock(buffer, bytesCount);
|
largeObject.Header.PayloadLength += (ulong)bytesCount;
|
||||||
|
|
||||||
currentObject = new FrostFsObject(header.ContainerId, bytesCount < objectSize ? buffer[..bytesCount] : buffer)
|
currentObject = new FrostFsObject(header.ContainerId)
|
||||||
|
.SetPayload(bytesCount < objectSize ? buffer[..bytesCount] : buffer)
|
||||||
.SetSplit(split);
|
.SetSplit(split);
|
||||||
|
|
||||||
if (largeObject.PayloadLength == fullLength)
|
if (largeObject.PayloadLength == fullLength)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
objectId = await PutSingleObjectAsync(currentObject, ctx);
|
objectId = await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx) { SessionToken = token });
|
||||||
|
|
||||||
sentObjectIds.Add(objectId!);
|
sentObjectIds.Add(objectId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sentObjectIds.Count != 0)
|
if (sentObjectIds.Count != 0)
|
||||||
{
|
{
|
||||||
largeObject.AddAttributes(parameters.Header!.Attributes);
|
largeObject.AddAttributes(args.Header!.Attributes);
|
||||||
largeObject.CalculateHash();
|
|
||||||
|
|
||||||
currentObject.SetParent(largeObject);
|
currentObject.SetParent(largeObject);
|
||||||
|
|
||||||
objectId = await PutSingleObjectAsync(currentObject, ctx);
|
var putSingleObjectParams = new PrmPutSingleObject(currentObject, ctx) { SessionToken = token };
|
||||||
|
|
||||||
|
objectId = await PutSingleObjectAsync(putSingleObjectParams);
|
||||||
|
|
||||||
sentObjectIds.Add(objectId);
|
sentObjectIds.Add(objectId);
|
||||||
|
|
||||||
var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject)
|
var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject)
|
||||||
|
@ -236,22 +269,21 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
|
|
||||||
linkObject.Header.Attributes.Clear();
|
linkObject.Header.Attributes.Clear();
|
||||||
|
|
||||||
_ = await PutSingleObjectAsync(linkObject, ctx);
|
_ = await PutSingleObjectAsync(new PrmPutSingleObject(linkObject, ctx){ SessionToken = token });
|
||||||
|
|
||||||
return tools.CalculateObjectId(largeObject.Header);
|
return tools.CalculateObjectId(largeObject.Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentObject.AddAttributes(parameters.Header!.Attributes);
|
currentObject.AddAttributes(args.Header!.Attributes);
|
||||||
|
|
||||||
return await PutSingleObjectAsync(currentObject, ctx);
|
return await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ObjectId> PutStreamObject(PutObjectParameters parameters, Context ctx)
|
private async Task<ObjectId> PutStreamObject(PrmPutObject args)
|
||||||
{
|
{
|
||||||
var payload = parameters.Payload!;
|
var ctx = args.Context!;
|
||||||
var header = parameters.Header!;
|
var payload = args.Payload!;
|
||||||
|
var header = args.Header!;
|
||||||
var sessionToken = await GetOrCreateSession(ctx);
|
|
||||||
|
|
||||||
var hdr = header.ToGrpcMessage();
|
var hdr = header.ToGrpcMessage();
|
||||||
hdr.OwnerId = Context.Owner.ToGrpcMessage();
|
hdr.OwnerId = Context.Owner.ToGrpcMessage();
|
||||||
|
@ -270,20 +302,21 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initRequest.AddMetaHeader();
|
var sessionToken = await GetOrCreateSession(args, ctx);
|
||||||
initRequest.AddObjectSessionToken(
|
|
||||||
sessionToken,
|
sessionToken.CreateObjectTokenContext(
|
||||||
hdr.ContainerId,
|
new Address { ContainerId = hdr.ContainerId, ObjectId = oid },
|
||||||
oid,
|
|
||||||
ObjectSessionContext.Types.Verb.Put,
|
ObjectSessionContext.Types.Verb.Put,
|
||||||
Context.Key
|
Context.Key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
initRequest.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
initRequest.Sign(Context.Key);
|
initRequest.Sign(Context.Key);
|
||||||
|
|
||||||
using var stream = await PutObjectInit(initRequest, ctx);
|
using var stream = await PutObjectInit(initRequest, ctx);
|
||||||
|
|
||||||
var bufferSize = parameters.BufferMaxSize > 0 ? parameters.BufferMaxSize : Constants.ObjectChunkSize;
|
var bufferSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize;
|
||||||
|
|
||||||
if (payload.CanSeek)
|
if (payload.CanSeek)
|
||||||
{
|
{
|
||||||
|
@ -307,12 +340,13 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
{
|
{
|
||||||
Body = new PutRequest.Types.Body
|
Body = new PutRequest.Types.Body
|
||||||
{
|
{
|
||||||
Chunk = ByteString.CopyFrom(buffer[..bytesCount]),
|
Chunk = ByteString.CopyFrom(buffer.AsSpan()[..bytesCount]),
|
||||||
},
|
},
|
||||||
VerifyHeader = null
|
VerifyHeader = null
|
||||||
};
|
};
|
||||||
|
|
||||||
chunkRequest.Sign(Context.Key);
|
chunkRequest.Sign(Context.Key);
|
||||||
|
|
||||||
await stream.Write(chunkRequest);
|
await stream.Write(chunkRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,13 +422,13 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
return new SearchReader(call);
|
return new SearchReader(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Session.SessionToken> GetOrCreateSession(Context ctx)
|
private async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ctx.SessionToken))
|
if (args.SessionToken is null)
|
||||||
{
|
{
|
||||||
return await Context.Client.CreateSessionInternalAsync(uint.MaxValue, ctx);
|
return await Context.Client.CreateSessionInternalAsync(new PrmCreateSession(uint.MaxValue, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken();
|
return new Session.SessionToken().Deserialize(args.SessionToken.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
@ -15,41 +15,41 @@ internal class SessionServiceProvider : ContextAccessor
|
||||||
_sessionServiceClient = sessionServiceClient;
|
_sessionServiceClient = sessionServiceClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<SessionToken> CreateSessionAsync(ulong expiration, Context ctx)
|
internal async Task<SessionToken> CreateSessionAsync(PrmCreateSession args)
|
||||||
{
|
{
|
||||||
var request = new CreateRequest
|
var request = new CreateRequest
|
||||||
{
|
{
|
||||||
Body = new CreateRequest.Types.Body
|
Body = new CreateRequest.Types.Body
|
||||||
{
|
{
|
||||||
OwnerId = Context.Owner.ToGrpcMessage(),
|
OwnerId = Context.Owner.ToGrpcMessage(),
|
||||||
Expiration = expiration
|
Expiration = args.Expiration
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader();
|
request.AddMetaHeader(args.XHeaders);
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var token = await CreateSession(request, ctx);
|
return await CreateSession(request, args.Context!);
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<SessionToken> CreateSession(CreateRequest request, Context ctx)
|
internal async Task<SessionToken> CreateSession(CreateRequest request, Context ctx)
|
||||||
{
|
{
|
||||||
var resp = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
return new SessionToken
|
return new SessionToken
|
||||||
{
|
{
|
||||||
Body = new SessionToken.Types.Body
|
Body = new SessionToken.Types.Body
|
||||||
{
|
{
|
||||||
Id = resp.Body.Id,
|
Id = response.Body.Id,
|
||||||
SessionKey = resp.Body.SessionKey,
|
SessionKey = response.Body.SessionKey,
|
||||||
OwnerId = request.Body.OwnerId,
|
OwnerId = request.Body.OwnerId,
|
||||||
Lifetime = new SessionToken.Types.Body.Types.TokenLifetime
|
Lifetime = new SessionToken.Types.Body.Types.TokenLifetime
|
||||||
{
|
{
|
||||||
Exp = request.Body.Expiration,
|
Exp = request.Body.Expiration,
|
||||||
Iat = resp.MetaHeader.Epoch,
|
Iat = response.MetaHeader.Epoch,
|
||||||
Nbf = resp.MetaHeader.Epoch,
|
Nbf = response.MetaHeader.Epoch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,12 @@ public static class ObjectExtensions
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FrostFsObject SetPayload(this FrostFsObject obj, byte[] bytes)
|
||||||
|
{
|
||||||
|
obj.Payload = bytes;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value)
|
public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value)
|
||||||
{
|
{
|
||||||
obj.AddAttribute(new ObjectAttribute(key, value));
|
obj.AddAttribute(new ObjectAttribute(key, value));
|
||||||
|
@ -45,9 +51,6 @@ public static class ObjectExtensions
|
||||||
|
|
||||||
public static FrostFsObject CalculateObjectId(this FrostFsObject obj)
|
public static FrostFsObject CalculateObjectId(this FrostFsObject obj)
|
||||||
{
|
{
|
||||||
if (obj.Payload == null)
|
|
||||||
throw new MissingFieldException("Payload cannot be null");
|
|
||||||
|
|
||||||
if (obj.Header == null)
|
if (obj.Header == null)
|
||||||
throw new MissingFieldException("Header cannot be null");
|
throw new MissingFieldException("Header cannot be null");
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
@ -9,36 +12,38 @@ namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public static class RequestConstructor
|
public static class RequestConstructor
|
||||||
{
|
{
|
||||||
public static void AddMetaHeader(this IRequest request, RequestMetaHeader? metaHeader = null)
|
public static void AddMetaHeader(this IRequest request, NameValueCollection? xHeaders, Session.SessionToken? sessionToken = null)
|
||||||
{
|
{
|
||||||
if (request.MetaHeader is not null) return;
|
if (request.MetaHeader is not null)
|
||||||
metaHeader ??= MetaHeader.Default().ToGrpcMessage();
|
return;
|
||||||
request.MetaHeader = metaHeader;
|
|
||||||
|
request.MetaHeader = MetaHeader.Default().ToGrpcMessage();
|
||||||
|
|
||||||
|
if (sessionToken != null)
|
||||||
|
request.MetaHeader.SessionToken = sessionToken;
|
||||||
|
|
||||||
|
if (xHeaders != null && xHeaders.Count > 0)
|
||||||
|
request.MetaHeader.XHeaders.AddRange(
|
||||||
|
xHeaders.Cast<string>().SelectMany(key => xHeaders.GetValues(key),
|
||||||
|
(k, v) => new XHeader { Key = k, Value = v }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddObjectSessionToken(
|
public static void CreateObjectTokenContext(this Session.SessionToken sessionToken,
|
||||||
this IRequest request,
|
Address address,
|
||||||
Session.SessionToken sessionToken,
|
|
||||||
ContainerID cid,
|
|
||||||
ObjectID oid,
|
|
||||||
ObjectSessionContext.Types.Verb verb,
|
ObjectSessionContext.Types.Verb verb,
|
||||||
ECDsa key)
|
ECDsa key)
|
||||||
{
|
{
|
||||||
if (request.MetaHeader.SessionToken is not null)
|
ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId };
|
||||||
return;
|
|
||||||
|
|
||||||
request.MetaHeader.SessionToken = sessionToken;
|
if (address.ObjectId != null)
|
||||||
var ctx = new ObjectSessionContext
|
target.Objects.Add(address.ObjectId);
|
||||||
|
|
||||||
|
sessionToken.Body.Object = new()
|
||||||
{
|
{
|
||||||
Target = new ObjectSessionContext.Types.Target
|
Target = target,
|
||||||
{
|
|
||||||
Container = cid,
|
|
||||||
Objects = { oid }
|
|
||||||
},
|
|
||||||
Verb = verb
|
Verb = verb
|
||||||
};
|
};
|
||||||
|
|
||||||
request.MetaHeader.SessionToken.Body.Object = ctx;
|
sessionToken.Signature = key.SignMessagePart(sessionToken.Body);
|
||||||
request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ public static class Verifier
|
||||||
|
|
||||||
var status = resp.MetaHeader.Status.ToModel();
|
var status = resp.MetaHeader.Status.ToModel();
|
||||||
if (!status.IsSuccess)
|
if (!status.IsSuccess)
|
||||||
throw new ApplicationException(status.ToString());
|
throw new ResponseException(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -12,8 +12,6 @@ public class Context()
|
||||||
|
|
||||||
public CancellationToken CancellationToken { get; set; } = default;
|
public CancellationToken CancellationToken { get; set; } = default;
|
||||||
public TimeSpan Timeout { get; set; } = default;
|
public TimeSpan Timeout { get; set; } = default;
|
||||||
public string SessionToken { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
||||||
|
|
||||||
public Action<CallStatistics>? Callback { get; set; }
|
public Action<CallStatistics>? Callback { get; set; }
|
||||||
|
|
|
@ -18,4 +18,9 @@
|
||||||
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||||
|
<_Parameter1>FrostFS.SDK.ClientV2</_Parameter1>
|
||||||
|
|||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -6,32 +6,56 @@ namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
public class FrostFsObject
|
public class FrostFsObject
|
||||||
{
|
{
|
||||||
public FrostFsObject(ObjectId objectId, ObjectHeader header, byte[] payload)
|
/// <summary>
|
||||||
|
/// Creates new instance from <c>ObjectHeader</c>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header"></param> <summary>
|
||||||
|
public FrostFsObject(ObjectHeader header)
|
||||||
{
|
{
|
||||||
ObjectId = objectId;
|
|
||||||
Payload = payload;
|
|
||||||
Header = header;
|
Header = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrostFsObject(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular)
|
/// <summary>
|
||||||
|
/// Creates new instance with specified parameters
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container"></param>
|
||||||
|
/// <param name="objectType"></param>
|
||||||
|
public FrostFsObject(ContainerId container, ObjectType objectType = ObjectType.Regular)
|
||||||
{
|
{
|
||||||
Payload = payload;
|
Header = new ObjectHeader(containerId: container, type: objectType);
|
||||||
Header = new ObjectHeader(containerId: container, type: objectType, attributes: []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header contains metadata for the object
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
public ObjectHeader Header { get; set; }
|
public ObjectHeader Header { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed.
|
||||||
|
/// </summary>
|
||||||
public ObjectId? ObjectId
|
public ObjectId? ObjectId
|
||||||
{
|
{
|
||||||
get; internal set;
|
get; internal set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] Payload { get; set; }
|
/// <summary>
|
||||||
|
/// The size of payload cannot exceed <c>MaxObjectSize</c> value from <c>NetworkSettings</c>
|
||||||
|
/// Used only for PutSingleObject method
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Buffer for output data</value>
|
||||||
|
public byte[] Payload { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A payload is obtained via stream reader
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Reader for received data</value>
|
||||||
public IObjectReader? ObjectReader { get; set; }
|
public IObjectReader? ObjectReader { get; set; }
|
||||||
|
|
||||||
public Signature? Signature { get; set; }
|
/// <summary>
|
||||||
|
/// Applied only for the last Object in chain in case of manual multipart uploading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="largeObject">Parent for multipart object</param>
|
||||||
public void SetParent(LargeObject largeObject)
|
public void SetParent(LargeObject largeObject)
|
||||||
{
|
{
|
||||||
if (Header?.Split == null)
|
if (Header?.Split == null)
|
||||||
|
@ -41,7 +65,7 @@ public class FrostFsObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LargeObject(ContainerId container) : FrostFsObject(container, [])
|
public class LargeObject(ContainerId container) : FrostFsObject(container)
|
||||||
{
|
{
|
||||||
private readonly SHA256 payloadHash = SHA256.Create();
|
private readonly SHA256 payloadHash = SHA256.Create();
|
||||||
|
|
||||||
|
@ -66,7 +90,7 @@ public class LargeObject(ContainerId container) : FrostFsObject(container, [])
|
||||||
|
|
||||||
public class LinkObject : FrostFsObject
|
public class LinkObject : FrostFsObject
|
||||||
{
|
{
|
||||||
public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, [])
|
public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId)
|
||||||
{
|
{
|
||||||
Header!.Split = new Split(splitId)
|
Header!.Split = new Split(splitId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +1 @@
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
public class PutObjectParameters
|
|
||||||
{
|
|
||||||
public ObjectHeader? Header { get; set; }
|
|
||||||
|
|
||||||
public Stream? Payload { get; set; }
|
|
||||||
|
|
||||||
public bool ClientCut { get; set; }
|
|
||||||
|
|
||||||
public int BufferMaxSize { get; set; }
|
|
||||||
}
|
|
|
@ -1,8 +1,11 @@
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
public class SessionToken(byte[] sessionKey, byte[] id)
|
public class SessionToken
|
||||||
{
|
{
|
||||||
public byte[] Id { get; } = id;
|
internal SessionToken(byte[] token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] SessionKey { get; } = sessionKey;
|
public byte[] Token { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
using FrostFS.SDK.ModelsV2.Enums;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
namespace FrostFS.SDK.Tests;
|
namespace FrostFS.SDK.Tests;
|
||||||
|
|
||||||
|
|
||||||
public abstract class ContainerTestsBase
|
public abstract class ContainerTestsBase
|
||||||
{
|
{
|
||||||
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
|
@ -52,7 +52,9 @@ public class ContainerTest : ContainerTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void CreateContainerTest()
|
public async void CreateContainerTest()
|
||||||
{
|
{
|
||||||
var result = await GetClient().CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy));
|
var param = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy));
|
||||||
|
|
||||||
|
var result = await GetClient().CreateContainerAsync(param);
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotNull(result.Value);
|
Assert.NotNull(result.Value);
|
||||||
|
@ -66,7 +68,7 @@ public class ContainerTest : ContainerTestsBase
|
||||||
|
|
||||||
Mocker.Acl = BasicAcl.PublicRO;
|
Mocker.Acl = BasicAcl.PublicRO;
|
||||||
|
|
||||||
var result = await GetClient().GetContainerAsync(cid);
|
var result = await GetClient().GetContainerAsync(new PrmGetContainer(cid));
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(Mocker.Acl, result.BasicAcl);
|
Assert.Equal(Mocker.Acl, result.BasicAcl);
|
||||||
|
@ -99,9 +101,10 @@ public class ContainerTest : ContainerTestsBase
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void DeleteContainerAsyncTest()
|
public async void DeleteContainerAsyncTest()
|
||||||
{
|
{
|
||||||
|
Mocker.ReturnContainerRemoved = true;
|
||||||
var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
|
var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes()));
|
||||||
|
|
||||||
await GetClient().DeleteContainerAsync(cid);
|
await GetClient().DeleteContainerAsync(new PrmDeleteContainer(cid));
|
||||||
|
|
||||||
Assert.Single(Mocker.Requests);
|
Assert.Single(Mocker.Requests);
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,21 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
|
||||||
|
|
||||||
getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse);
|
getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse);
|
||||||
|
|
||||||
|
var getNoContainerResponse = new GetResponse
|
||||||
|
{
|
||||||
|
Body = new (),
|
||||||
|
MetaHeader = new ResponseMetaHeader
|
||||||
|
{
|
||||||
|
Status = new Status.Status
|
||||||
|
{
|
||||||
|
Code = 3072,
|
||||||
|
Message = "container not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getNoContainerResponse.VerifyHeader = GetResponseVerificationHeader(getNoContainerResponse);
|
||||||
|
|
||||||
mock.Setup(x => x.GetAsync(
|
mock.Setup(x => x.GetAsync(
|
||||||
It.IsAny<GetRequest>(),
|
It.IsAny<GetRequest>(),
|
||||||
It.IsAny<Metadata>(),
|
It.IsAny<Metadata>(),
|
||||||
|
@ -86,6 +101,16 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
|
||||||
{
|
{
|
||||||
Verifier.CheckRequest(r);
|
Verifier.CheckRequest(r);
|
||||||
|
|
||||||
|
if (ReturnContainerRemoved)
|
||||||
|
{
|
||||||
|
return new AsyncUnaryCall<GetResponse>(
|
||||||
|
Task.FromResult(getNoContainerResponse),
|
||||||
|
Task.FromResult(ResponseMetaData),
|
||||||
|
() => new Grpc.Core.Status(StatusCode.NotFound, string.Empty),
|
||||||
|
() => ResponseMetaData,
|
||||||
|
() => { });
|
||||||
|
}
|
||||||
|
|
||||||
return new AsyncUnaryCall<GetResponse>(
|
return new AsyncUnaryCall<GetResponse>(
|
||||||
Task.FromResult(getResponse),
|
Task.FromResult(getResponse),
|
||||||
Task.FromResult(ResponseMetaData),
|
Task.FromResult(ResponseMetaData),
|
||||||
|
@ -154,6 +179,8 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
|
||||||
return mock;
|
return mock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ReturnContainerRemoved { get; set; }
|
||||||
|
|
||||||
public List<byte[]> ContainerIds { get; set; } = [];
|
public List<byte[]> ContainerIds { get; set; } = [];
|
||||||
|
|
||||||
public List<RequestData<DeleteRequest>> Requests { get; set; } = [];
|
public List<RequestData<DeleteRequest>> Requests { get; set; } = [];
|
||||||
|
|
|
@ -2,6 +2,7 @@ using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Interfaces;
|
using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
using FrostFS.SDK.ModelsV2.Enums;
|
||||||
|
@ -76,7 +77,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
Timeout = TimeSpan.FromSeconds(2)
|
Timeout = TimeSpan.FromSeconds(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await client.GetObjectAsync(ContainerId, objectId, context);
|
var result = await client.GetObjectAsync(new PrmGetObject(ContainerId, objectId) { Context = context });
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
var bytes = new byte[1024];
|
var bytes = new byte[1024];
|
||||||
rnd.NextBytes(bytes);
|
rnd.NextBytes(bytes);
|
||||||
|
|
||||||
var param = new PutObjectParameters
|
var param = new PrmPutObject
|
||||||
{
|
{
|
||||||
Header = Mocker.ObjectHeader,
|
Header = Mocker.ObjectHeader,
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
|
@ -132,7 +133,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg");
|
byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg");
|
||||||
var fileLength = bytes.Length;
|
var fileLength = bytes.Length;
|
||||||
|
|
||||||
var param = new PutObjectParameters
|
var param = new PrmPutObject
|
||||||
{
|
{
|
||||||
Header = Mocker.ObjectHeader,
|
Header = Mocker.ObjectHeader,
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
|
@ -196,7 +197,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
{
|
{
|
||||||
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
||||||
|
|
||||||
await GetClient().DeleteObjectAsync(ContainerId, Mocker.ObjectId);
|
await GetClient().DeleteObjectAsync(new PrmDeleteObject(ContainerId, Mocker.ObjectId));
|
||||||
|
|
||||||
var request = Mocker.DeleteRequests.FirstOrDefault();
|
var request = Mocker.DeleteRequests.FirstOrDefault();
|
||||||
Assert.NotNull(request);
|
Assert.NotNull(request);
|
||||||
|
@ -209,7 +210,7 @@ public class ObjectTest : ObjectTestsBase
|
||||||
{
|
{
|
||||||
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
||||||
|
|
||||||
var response = await GetClient().GetObjectHeadAsync(ContainerId, Mocker.ObjectId);
|
var response = await GetClient().GetObjectHeadAsync(new PrmGetObjectHead(ContainerId, Mocker.ObjectId));
|
||||||
|
|
||||||
var request = Mocker.HeadRequests.FirstOrDefault();
|
var request = Mocker.HeadRequests.FirstOrDefault();
|
||||||
Assert.NotNull(request);
|
Assert.NotNull(request);
|
||||||
|
|
|
@ -4,24 +4,30 @@ using FrostFS.SDK.ClientV2.Interfaces;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
using FrostFS.SDK.ModelsV2.Enums;
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
using FrostFS.SDK.ModelsV2.Netmap;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Grpc.Core.Interceptors;
|
using Grpc.Core.Interceptors;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using static FrostFS.Session.SessionToken.Types.Body;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
namespace FrostFS.SDK.SmokeTests;
|
namespace FrostFS.SDK.SmokeTests;
|
||||||
|
|
||||||
public class SmokeTests
|
public class SmokeTests
|
||||||
{
|
{
|
||||||
|
private static PrmWait lightWait = new (100, 1);
|
||||||
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
private readonly string url = "http://172.29.238.97:8080";
|
private readonly string url = "http://172.29.238.97:8080";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NetworkMapTest()
|
public async void NetworkMapTest()
|
||||||
{
|
{
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
var result = await fsClient.GetNetmapSnapshotAsync();
|
var result = await client.GetNetmapSnapshotAsync();
|
||||||
|
|
||||||
Assert.True(result.Epoch > 0);
|
Assert.True(result.Epoch > 0);
|
||||||
Assert.Single(result.NodeInfoCollection);
|
Assert.Single(result.NodeInfoCollection);
|
||||||
|
@ -38,9 +44,9 @@ public class SmokeTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NodeInfoTest()
|
public async void NodeInfoTest()
|
||||||
{
|
{
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
var result = await fsClient.GetNodeInfoAsync();
|
var result = await client.GetNodeInfoAsync();
|
||||||
|
|
||||||
Assert.Equal(2, result.Version.Major);
|
Assert.Equal(2, result.Version.Major);
|
||||||
Assert.Equal(13, result.Version.Minor);
|
Assert.Equal(13, result.Version.Minor);
|
||||||
|
@ -51,80 +57,74 @@ public class SmokeTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NodeInfo_Statictics_Test()
|
public async void NodeInfo_Statistics_Test()
|
||||||
{
|
{
|
||||||
var ctx = new Context
|
var ctx = new Context
|
||||||
{
|
{
|
||||||
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
|
Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds")
|
||||||
};
|
};
|
||||||
|
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
var result = await fsClient.GetNodeInfoAsync();
|
var result = await client.GetNodeInfoAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(1)]
|
public async void GetSessionTest()
|
||||||
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
|
||||||
[InlineData(6 * 1024 * 1024 + 100)]
|
|
||||||
public async void SimpleScenarioTest(int objectSize)
|
|
||||||
{
|
{
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
var token = await client.CreateSessionAsync(new PrmCreateSession(100));
|
||||||
|
|
||||||
var containerId = await fsClient.CreateContainerAsync(
|
var session = new Session.SessionToken().Deserialize(token.Token);
|
||||||
new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))),
|
|
||||||
new Context
|
var ecdsaKey = this.key.LoadWif();
|
||||||
{
|
var owner = OwnerId.FromKey(ecdsaKey);
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
|
var ownerHash = Base58.Decode(owner.Value);
|
||||||
|
|
||||||
|
Assert.NotNull(session);
|
||||||
|
Assert.Null(session.Body.Container);
|
||||||
|
Assert.Null(session.Body.Object);
|
||||||
|
Assert.Equal(16, session.Body.Id.Length);
|
||||||
|
Assert.Equal(100ul, session.Body.Lifetime.Exp);
|
||||||
|
Assert.Equal(ownerHash, session.Body.OwnerId.Value);
|
||||||
|
Assert.Equal(33, session.Body.SessionKey.Length);
|
||||||
|
Assert.Equal(ContextOneofCase.None, session.Body.ContextCase);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
var context = new Context
|
[Fact]
|
||||||
|
public async void CreateObjectWithSessionToken()
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10),
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
};
|
|
||||||
|
|
||||||
var container = await GetContainer(fsClient, containerId, context);
|
await Cleanup(client);
|
||||||
|
|
||||||
Assert.NotNull(container);
|
var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue));
|
||||||
|
|
||||||
var bytes = GetRandomBytes(objectSize);
|
var createContainerParam = new PrmCreateContainer(
|
||||||
|
new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))));
|
||||||
|
|
||||||
var param = new PutObjectParameters
|
createContainerParam.XHeaders.Add("key1", "value1");
|
||||||
|
|
||||||
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(1024);
|
||||||
|
|
||||||
|
var param = new PrmPutObject
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
type: ObjectType.Regular,
|
type: ObjectType.Regular,
|
||||||
new ObjectAttribute("fileName", "test")),
|
new ObjectAttribute("fileName", "test")),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false
|
ClientCut = false,
|
||||||
|
SessionToken = token
|
||||||
};
|
};
|
||||||
|
|
||||||
var objectId = await fsClient.PutObjectAsync(param, new Context
|
var objectId = await client.PutObjectAsync(param);
|
||||||
{
|
|
||||||
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
|
||||||
});
|
|
||||||
|
|
||||||
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId));
|
||||||
|
|
||||||
bool hasObject = false;
|
|
||||||
await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter]))
|
|
||||||
{
|
|
||||||
hasObject = true;
|
|
||||||
|
|
||||||
var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId);
|
|
||||||
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
|
||||||
Assert.Single(objHeader.Attributes);
|
|
||||||
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
|
||||||
Assert.Equal("test", objHeader.Attributes.First().Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.True(hasObject);
|
|
||||||
|
|
||||||
var @object = await fsClient.GetObjectAsync(containerId, objectId!);
|
|
||||||
|
|
||||||
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
MemoryStream ms = new(downloadedBytes);
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
@ -137,11 +137,177 @@ public class SmokeTests
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
await Cleanup(client);
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Delay(2000);
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||||
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
|
public async void SimpleScenarioTest(int objectSize)
|
||||||
|
{
|
||||||
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
await foreach (var _ in fsClient.ListContainersAsync())
|
await Cleanup(client);
|
||||||
|
|
||||||
|
bool callbackInvoked = false;
|
||||||
|
var ctx = new Context
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
Callback = new((CallStatistics cs) =>
|
||||||
|
{
|
||||||
|
callbackInvoked = true;
|
||||||
|
Assert.True(cs.ElapsedMicroSeconds > 0);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var createContainerParam = new PrmCreateContainer(
|
||||||
|
new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))))
|
||||||
|
{
|
||||||
|
Context = ctx
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx));
|
||||||
|
Assert.NotNull(container);
|
||||||
|
Assert.True(callbackInvoked);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
|
var param = new PrmPutObject
|
||||||
|
{
|
||||||
|
Header = new ObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: ObjectType.Regular,
|
||||||
|
new ObjectAttribute("fileName", "test")),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false,
|
||||||
|
Context = new Context
|
||||||
|
{
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
bool hasObject = false;
|
||||||
|
await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter] }))
|
||||||
|
{
|
||||||
|
hasObject = true;
|
||||||
|
|
||||||
|
var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId));
|
||||||
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.Single(objHeader.Attributes);
|
||||||
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
|
Assert.Equal("test", objHeader.Attributes.First().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(hasObject);
|
||||||
|
|
||||||
|
var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId));
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
byte[]? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await Cleanup(client);
|
||||||
|
|
||||||
|
await foreach (var _ in client.ListContainersAsync())
|
||||||
|
{
|
||||||
|
Assert.Fail("Containers exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||||
|
[InlineData(6 * 1024 * 1024 + 100)]
|
||||||
|
public async void SimpleScenarioWithSessionTest(int objectSize)
|
||||||
|
{
|
||||||
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
|
var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue));
|
||||||
|
|
||||||
|
await Cleanup(client);
|
||||||
|
|
||||||
|
var ctx = new Context
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
};
|
||||||
|
|
||||||
|
var createContainerParam = new PrmCreateContainer(
|
||||||
|
new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))))
|
||||||
|
{
|
||||||
|
Context = ctx
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx));
|
||||||
|
Assert.NotNull(container);
|
||||||
|
|
||||||
|
var bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
|
var param = new PrmPutObject
|
||||||
|
{
|
||||||
|
Header = new ObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: ObjectType.Regular,
|
||||||
|
new ObjectAttribute("fileName", "test")),
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false,
|
||||||
|
Context = new Context
|
||||||
|
{
|
||||||
|
Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0))
|
||||||
|
},
|
||||||
|
SessionToken = token
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
bool hasObject = false;
|
||||||
|
await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter], SessionToken = token }))
|
||||||
|
{
|
||||||
|
hasObject = true;
|
||||||
|
|
||||||
|
var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId) { SessionToken = token });
|
||||||
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
|
Assert.Single(objHeader.Attributes);
|
||||||
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
|
Assert.Equal("test", objHeader.Attributes.First().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(hasObject);
|
||||||
|
|
||||||
|
var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId) { SessionToken = token });
|
||||||
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
byte[]? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await Cleanup(client);
|
||||||
|
|
||||||
|
await foreach (var _ in client.ListContainersAsync())
|
||||||
{
|
{
|
||||||
Assert.Fail("Containers exist");
|
Assert.Fail("Containers exist");
|
||||||
}
|
}
|
||||||
|
@ -155,13 +321,16 @@ public class SmokeTests
|
||||||
[InlineData(2 * 64 * 1024 * 1024 + 256)]
|
[InlineData(2 * 64 * 1024 * 1024 + 256)]
|
||||||
public async void ClientCutScenarioTest(int objectSize)
|
public async void ClientCutScenarioTest(int objectSize)
|
||||||
{
|
{
|
||||||
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
await Cleanup(client);
|
||||||
|
|
||||||
var cnt = new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)));
|
var createContainerParam = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))))
|
||||||
|
{
|
||||||
|
WaitParams = lightWait
|
||||||
|
};
|
||||||
|
|
||||||
var containerId = await fsClient.CreateContainerAsync(cnt);
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
var context = new Context
|
var context = new Context
|
||||||
{
|
{
|
||||||
|
@ -169,13 +338,13 @@ public class SmokeTests
|
||||||
Interceptors = new([new MetricsInterceptor()])
|
Interceptors = new([new MetricsInterceptor()])
|
||||||
};
|
};
|
||||||
|
|
||||||
var container = await GetContainer(fsClient, containerId, context);
|
var container = await client.GetContainerAsync(new PrmGetContainer(containerId, context));
|
||||||
|
|
||||||
Assert.NotNull(container);
|
Assert.NotNull(container);
|
||||||
|
|
||||||
byte[] bytes = GetRandomBytes(objectSize);
|
byte[] bytes = GetRandomBytes(objectSize);
|
||||||
|
|
||||||
var param = new PutObjectParameters
|
var param = new PrmPutObject
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
|
@ -185,16 +354,16 @@ public class SmokeTests
|
||||||
ClientCut = true
|
ClientCut = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var objectId = await fsClient.PutObjectAsync(param);
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
bool hasObject = false;
|
||||||
await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter]))
|
await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId, filter)))
|
||||||
{
|
{
|
||||||
hasObject = true;
|
hasObject = true;
|
||||||
|
|
||||||
var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId);
|
var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId));
|
||||||
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
|
||||||
Assert.Single(objHeader.Attributes);
|
Assert.Single(objHeader.Attributes);
|
||||||
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
Assert.Equal("fileName", objHeader.Attributes.First().Key);
|
||||||
|
@ -203,7 +372,7 @@ public class SmokeTests
|
||||||
|
|
||||||
Assert.True(hasObject);
|
Assert.True(hasObject);
|
||||||
|
|
||||||
var @object = await fsClient.GetObjectAsync(containerId, objectId!);
|
var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId));
|
||||||
|
|
||||||
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
MemoryStream ms = new(downloadedBytes);
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
@ -216,14 +385,23 @@ public class SmokeTests
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
||||||
|
|
||||||
await Cleanup(fsClient);
|
await Cleanup(client);
|
||||||
|
|
||||||
await Task.Delay(2000);
|
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
await foreach (var _ in fsClient.ListContainersAsync())
|
IAsyncEnumerator<ContainerId>? enumerator = null;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (deadline <= DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
Assert.Fail("Containers exist");
|
Assert.Fail("Containers exist");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enumerator = client.ListContainersAsync().GetAsyncEnumerator();
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
while (await enumerator!.MoveNextAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetRandomBytes(int size)
|
private static byte[] GetRandomBytes(int size)
|
||||||
|
@ -243,36 +421,16 @@ public class SmokeTests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task Cleanup(IFrostFSClient fsClient)
|
static async Task Cleanup(IFrostFSClient client)
|
||||||
{
|
{
|
||||||
await foreach (var cid in fsClient.ListContainersAsync())
|
await foreach (var cid in client.ListContainersAsync())
|
||||||
{
|
{
|
||||||
await fsClient.DeleteContainerAsync(cid);
|
await client.DeleteContainerAsync(new PrmDeleteContainer(cid) { WaitParams = lightWait });
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<ModelsV2.Container> GetContainer(IFrostFSClient fsClient, ContainerId id, Context ctx)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
return await fsClient.GetContainerAsync(id, ctx);
|
|
||||||
}
|
|
||||||
catch (ApplicationException)
|
|
||||||
{
|
|
||||||
if (DateTime.UtcNow >= ctx.Deadline)
|
|
||||||
throw new TimeoutException();
|
|
||||||
}
|
|
||||||
catch (RpcException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class MetricsInterceptor() : Interceptor
|
public class MetricsInterceptor() : Interceptor
|
||||||
{
|
{
|
||||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
|
Loading…
Reference in a new issue
Looks like dirty hack. As far as I remember, this should be used only for unit tests.