[#19] Client: Use specific classes for searching #20
27 changed files with 320 additions and 123 deletions
|
@ -7,7 +7,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
|
||||||
public static class ObjectFilterMapper
|
public static class ObjectFilterMapper
|
||||||
{
|
{
|
||||||
public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter)
|
public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this IObjectFilter filter)
|
||||||
{
|
{
|
||||||
var objMatchTypeName = filter.MatchType switch
|
var objMatchTypeName = filter.MatchType switch
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ public static class ObjectFilterMapper
|
||||||
{
|
{
|
||||||
MatchType = objMatchTypeName,
|
MatchType = objMatchTypeName,
|
||||||
Key = filter.Key,
|
Key = filter.Key,
|
||||||
Value = filter.Value
|
Value = filter.GetSerializedValue()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ public static class ObjectHeaderMapper
|
||||||
|
|
||||||
if (header.Split != null)
|
if (header.Split != null)
|
||||||
{
|
{
|
||||||
model.Split = new Split(SplitId.CreateFromBinary(header.Split.SplitId.ToByteArray()))
|
model.Split = new Split(new SplitId(header.Split.SplitId.ToUuid()))
|
||||||
{
|
{
|
||||||
Parent = header.Split.Parent?.ToModel(),
|
Parent = header.Split.Parent?.ToModel(),
|
||||||
ParentHeader = header.Split.ParentHeader?.ToModel(),
|
ParentHeader = header.Split.ParentHeader?.ToModel(),
|
||||||
|
@ -86,5 +86,5 @@ public static class ObjectHeaderMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
public sealed class PrmContainerGetAll() : IContext
|
public sealed class PrmContainerGetAll() : IContext
|
||||||
{
|
{
|
||||||
public string SessionToken { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// FrostFS request X-Headers
|
/// FrostFS request X-Headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -3,15 +3,19 @@ using System.Collections.Specialized;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
namespace FrostFS.SDK.ClientV2.Parameters;
|
namespace FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
public sealed class PrmObjectSearch(ContainerId containerId, params ObjectFilter[] filters) : IContext, ISessionToken
|
public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : IContext, ISessionToken
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines container for the search
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
public ContainerId ContainerId { get; set; } = containerId;
|
public ContainerId ContainerId { get; set; } = containerId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the search criteria
|
/// Defines the search criteria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>Collection of filters</value>
|
/// <value>Collection of filters</value>
|
||||||
public IEnumerable<ObjectFilter> Filters { get; set; } = filters;
|
public IEnumerable<IObjectFilter> Filters { get; set; } = filters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// FrostFS request X-Headers
|
/// FrostFS request X-Headers
|
||||||
|
|
|
@ -1,33 +1,25 @@
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap;
|
||||||
using FrostFS.Container;
|
using FrostFS.Container;
|
||||||
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.Cryptography;
|
using FrostFS.SDK.Cryptography;
|
||||||
using System.Collections.Generic;
|
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.Refs;
|
|
||||||
using System;
|
|
||||||
using FrostFS.SDK.ClientV2.Parameters;
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
using System.Collections.Specialized;
|
using FrostFS.Refs;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal class ContainerServiceProvider : ContextAccessor
|
internal class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context)
|
||||||
{
|
{
|
||||||
private readonly ContainerService.ContainerServiceClient containerServiceClient;
|
|
||||||
|
|
||||||
internal ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context)
|
|
||||||
: base(context)
|
|
||||||
{
|
|
||||||
containerServiceClient = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task<ModelsV2.Container> GetContainerAsync(PrmContainerGet args)
|
internal async Task<ModelsV2.Container> GetContainerAsync(PrmContainerGet args)
|
||||||
{
|
{
|
||||||
GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders);
|
GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders);
|
||||||
|
|
||||||
var response = await containerServiceClient.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
|
var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -49,7 +41,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
request.AddMetaHeader(args.XHeaders);
|
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 service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -74,11 +66,11 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
Signature = Context.Key.SignRFC6979(grpcContainer)
|
Signature = Context.Key.SignRFC6979(grpcContainer)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader(args.XHeaders);
|
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 service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -98,11 +90,12 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value)
|
Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddMetaHeader(args.XHeaders);
|
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 service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
|
|
||||||
await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx);
|
await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx);
|
||||||
|
|
||||||
|
@ -137,7 +130,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
|
|
||||||
async Task action()
|
async Task action()
|
||||||
{
|
{
|
||||||
var response = await containerServiceClient.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
var response = await service.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +141,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
Func<Task> action,
|
Func<Task> action,
|
||||||
WaitExpects expect,
|
WaitExpects expect,
|
||||||
PrmWait? waitParams)
|
PrmWait? waitParams)
|
||||||
{
|
{
|
||||||
waitParams ??= PrmWait.DefaultParams;
|
waitParams ??= PrmWait.DefaultParams;
|
||||||
var deadLine = waitParams.GetDeadline();
|
var deadLine = waitParams.GetDeadline();
|
||||||
|
|
||||||
|
@ -160,7 +153,10 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
|
|
||||||
if (expect == WaitExpects.Exists)
|
if (expect == WaitExpects.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (DateTime.UtcNow >= deadLine)
|
||||||
|
throw new TimeoutException();
|
||||||
|
|
||||||
await Task.Delay(waitParams.PollInterval);
|
await Task.Delay(waitParams.PollInterval);
|
||||||
}
|
}
|
||||||
catch (ResponseException ex)
|
catch (ResponseException ex)
|
||||||
|
@ -173,11 +169,9 @@ internal class ContainerServiceProvider : ContextAccessor
|
||||||
|
|
||||||
if (expect == WaitExpects.Removed)
|
if (expect == WaitExpects.Removed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Task.Delay(waitParams.PollInterval);
|
await Task.Delay(waitParams.PollInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,19 @@ 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;
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx)
|
internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx), ISessionProvider
|
||||||
{
|
{
|
||||||
readonly ObjectTools tools = new(ctx);
|
readonly ObjectTools tools = new(ctx);
|
||||||
|
readonly SessionProvider sessions = new (ctx);
|
||||||
|
|
||||||
|
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
||||||
|
{
|
||||||
|
return await sessions.GetOrCreateSession(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
internal async Task<ObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
internal async Task<ObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
||||||
{
|
{
|
||||||
|
@ -137,7 +142,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
Context.Key);
|
Context.Key);
|
||||||
|
|
||||||
request.AddMetaHeader(args.XHeaders, sessionToken);
|
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||||
|
|
||||||
request.Sign(Context.Key);
|
request.Sign(Context.Key);
|
||||||
|
|
||||||
var objectsIds = SearchObjects(request, ctx);
|
var objectsIds = SearchObjects(request, ctx);
|
||||||
|
@ -192,8 +197,6 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
|
|
||||||
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly AsyncLocal<Session.SessionToken> asyncLocalSession = new ();
|
|
||||||
|
|
||||||
private async Task<ObjectId> PutClientCutObject(PrmObjectPut args)
|
private async Task<ObjectId> PutClientCutObject(PrmObjectPut args)
|
||||||
{
|
{
|
||||||
|
@ -274,7 +277,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
return tools.CalculateObjectId(largeObject.Header);
|
return tools.CalculateObjectId(largeObject.Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentObject.AddAttributes(args.Header!.Attributes);
|
currentObject
|
||||||
|
.SetSplit(null)
|
||||||
|
.AddAttributes(args.Header!.Attributes);
|
||||||
|
|
||||||
return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx));
|
return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx));
|
||||||
}
|
}
|
||||||
|
@ -421,14 +426,4 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
||||||
|
|
||||||
return new SearchReader(call);
|
return new SearchReader(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
|
||||||
{
|
|
||||||
if (args.SessionToken is null)
|
|
||||||
{
|
|
||||||
return await Context.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Session.SessionToken().Deserialize(args.SessionToken.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
23
src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
Normal file
23
src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FrostFS.SDK.ClientV2.Parameters;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
internal interface ISessionProvider
|
||||||
|
{
|
||||||
|
ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SessionProvider(ClientEnvironment env)
|
||||||
|
{
|
||||||
|
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
|
||||||
|
{
|
||||||
|
if (args.SessionToken is null)
|
||||||
|
{
|
||||||
|
return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Session.SessionToken().Deserialize(args.SessionToken.Token);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ public static class ObjectExtensions
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FrostFsObject SetSplit(this FrostFsObject obj, Split split)
|
public static FrostFsObject SetSplit(this FrostFsObject obj, Split? split)
|
||||||
{
|
{
|
||||||
obj.Header.Split = split;
|
obj.Header.Split = split;
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -80,9 +80,7 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx)
|
||||||
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
|
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
|
||||||
grpcHeader.Version = Context.Version.ToGrpcMessage();
|
grpcHeader.Version = Context.Version.ToGrpcMessage();
|
||||||
|
|
||||||
if (header.PayloadCheckSum != null)
|
if (payload != null)
|
||||||
grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum);
|
|
||||||
else if (payload != null)
|
|
||||||
grpcHeader.PayloadHash = Sha256Checksum(payload);
|
grpcHeader.PayloadHash = Sha256Checksum(payload);
|
||||||
|
|
||||||
return grpcHeader;
|
return grpcHeader;
|
||||||
|
|
|
@ -7,18 +7,49 @@ public static class UUIDExtension
|
||||||
{
|
{
|
||||||
public static Guid ToUuid(this ByteString id)
|
public static Guid ToUuid(this ByteString id)
|
||||||
{
|
{
|
||||||
return Guid.Parse(BitConverter.ToString(id.ToByteArray()).Replace("-", ""));
|
var bytes = id.ToByteArray();
|
||||||
|
|
||||||
|
var orderedBytes = GetGuidBytesDirectOrder(bytes);
|
||||||
|
|
||||||
|
return new Guid(orderedBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes Guid to binary representation in direct order bytes format
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public static byte[] ToBytes(this Guid id)
|
public static byte[] ToBytes(this Guid id)
|
||||||
{
|
{
|
||||||
var str = id.ToString("N");
|
var bytes = id.ToByteArray();
|
||||||
var len = str.Length;
|
|
||||||
var bytes = new byte[len/2];
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i += 2)
|
var orderedBytes = GetGuidBytesDirectOrder(bytes);
|
||||||
bytes[i/2] = Convert.ToByte(str.Substring(i, 2), 16);
|
|
||||||
|
return orderedBytes;
|
||||||
return bytes;
|
}
|
||||||
|
|
||||||
|
private static byte[] GetGuidBytesDirectOrder(byte[] source)
|
||||||
|
{
|
||||||
|
if (source.Length != 16)
|
||||||
|
throw new ArgumentException("Wrong uuid binary format");
|
||||||
|
|
||||||
|
return [
|
||||||
|
source[3],
|
||||||
|
source[2],
|
||||||
|
source[1],
|
||||||
|
source[0],
|
||||||
|
source[5],
|
||||||
|
source[4],
|
||||||
|
source[7],
|
||||||
|
source[6],
|
||||||
|
source[8],
|
||||||
|
source[9],
|
||||||
|
source[10],
|
||||||
|
source[11],
|
||||||
|
source[12],
|
||||||
|
source[13],
|
||||||
|
source[14],
|
||||||
|
source[15]
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
public class CheckSum
|
public class CheckSum
|
||||||
{
|
{
|
||||||
|
// type is always Sha256
|
||||||
public byte[]? Hash { get; set; }
|
public byte[]? Hash { get; set; }
|
||||||
|
|
||||||
public static byte[] GetHash(byte[] content)
|
public static byte[] GetHash(byte[] content)
|
||||||
|
@ -20,6 +21,6 @@ public class CheckSum
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Encoding.UTF8.GetString(Hash);
|
return BitConverter.ToString(Hash).Replace("-", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -69,19 +69,6 @@ public class LargeObject(ContainerId container) : FrostFsObject(container)
|
||||||
{
|
{
|
||||||
private readonly SHA256 payloadHash = SHA256.Create();
|
private readonly SHA256 payloadHash = SHA256.Create();
|
||||||
|
|
||||||
public void AppendBlock(byte[] bytes, int count)
|
|
||||||
{
|
|
||||||
Header!.PayloadLength += (ulong)count;
|
|
||||||
this.payloadHash.TransformBlock(bytes, 0, count, bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LargeObject CalculateHash()
|
|
||||||
{
|
|
||||||
this.payloadHash.TransformFinalBlock([], 0, 0);
|
|
||||||
Header!.PayloadCheckSum = this.payloadHash.Hash;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong PayloadLength
|
public ulong PayloadLength
|
||||||
{
|
{
|
||||||
get { return Header!.PayloadLength; }
|
get { return Header!.PayloadLength; }
|
||||||
|
|
|
@ -2,37 +2,112 @@ using FrostFS.SDK.ModelsV2.Enums;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
public class ObjectFilter
|
public interface IObjectFilter
|
||||||
{
|
{
|
||||||
private const string HeaderPrefix = "$Object:";
|
|
||||||
public ObjectMatchType MatchType { get; set; }
|
public ObjectMatchType MatchType { get; set; }
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string Value { get; set; }
|
|
||||||
|
string? GetSerializedValue();
|
||||||
|
}
|
||||||
|
|
||||||
public ObjectFilter(ObjectMatchType matchType, string key, string value)
|
public abstract class ObjectFilter<T>(ObjectMatchType matchType, string key, T value) : IObjectFilter
|
||||||
{
|
{
|
||||||
MatchType = matchType;
|
public ObjectMatchType MatchType { get; set; } = matchType;
|
||||||
Key = key;
|
public string Key { get; set; } = key;
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectFilter ObjectIdFilter(ObjectMatchType matchType, ObjectId objectId)
|
public T Value { get; set; } = value;
|
||||||
{
|
|
||||||
return new ObjectFilter(matchType, HeaderPrefix + "objectID", objectId.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectFilter OwnerFilter(ObjectMatchType matchType, OwnerId ownerId)
|
public string? GetSerializedValue()
|
||||||
{
|
{
|
||||||
return new ObjectFilter(matchType, HeaderPrefix + "ownerID", ownerId.Value);
|
return Value?.ToString();
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectFilter RootFilter()
|
|
||||||
{
|
|
||||||
return new ObjectFilter(ObjectMatchType.Unspecified, HeaderPrefix + "ROOT", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectFilter VersionFilter(ObjectMatchType matchType, Version version)
|
|
||||||
{
|
|
||||||
return new ObjectFilter(matchType, HeaderPrefix + "version", version.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Attribute
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="key">Attribute key</param>
|
||||||
|
/// <param name="value">Attribute value</param>
|
||||||
|
public class FilterByAttribute(ObjectMatchType matchType, string key, string value) : ObjectFilter<string>(matchType, key, value) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by ObjectId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="objectId">ObjectId</param>
|
||||||
|
public class FilterByObjectId(ObjectMatchType matchType, ObjectId objectId) : ObjectFilter<ObjectId>(matchType, Constants.FilterHeaderObjectID, objectId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by OwnerId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="ownerId">ObjectId</param>
|
||||||
|
public class FilterByOwnerId(ObjectMatchType matchType, OwnerId ownerId) : ObjectFilter<OwnerId>(matchType, Constants.FilterHeaderOwnerID, ownerId) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="version">Version</param>
|
||||||
|
public class FilterByVersion(ObjectMatchType matchType, Version version) : ObjectFilter<Version>(matchType, Constants.FilterHeaderVersion, version) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by ContainerId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="containerId">ContainerId</param>
|
||||||
|
public class FilterByContainerId(ObjectMatchType matchType, ContainerId containerId) : ObjectFilter<ContainerId>(matchType, Constants.FilterHeaderContainerID, containerId) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by creation Epoch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="epoch">Creation Epoch</param>
|
||||||
|
public class FilterByEpoch(ObjectMatchType matchType, ulong epoch) : ObjectFilter<ulong>(matchType, Constants.FilterHeaderCreationEpoch, epoch) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="payloadLength">Payload Length</param>
|
||||||
|
public class FilterByPayloadLength(ObjectMatchType matchType, ulong payloadLength) : ObjectFilter<ulong>(matchType, Constants.FilterHeaderPayloadLength, payloadLength) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="payloadHash">Payload Hash</param>
|
||||||
|
public class FilterByPayloadHash(ObjectMatchType matchType, CheckSum payloadHash) : ObjectFilter<CheckSum>(matchType, Constants.FilterHeaderPayloadHash, payloadHash) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Parent
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="parentId">Parent</param>
|
||||||
|
public class FilterByParent(ObjectMatchType matchType, ObjectId parentId) : ObjectFilter<ObjectId>(matchType, Constants.FilterHeaderParent, parentId) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by SplitId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="splitId">SplitId</param>
|
||||||
|
public class FilterBySplitId(ObjectMatchType matchType, SplitId splitId) : ObjectFilter<SplitId>(matchType, Constants.FilterHeaderSplitID, splitId) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="ecParentId">Payload Hash</param>
|
||||||
|
public class FilterByECParent(ObjectMatchType matchType, ObjectId ecParentId) : ObjectFilter<ObjectId>(matchType, Constants.FilterHeaderECParent, ecParentId) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search Root objects
|
||||||
|
/// </summary>
|
||||||
|
public class FilterByRootObject() : ObjectFilter<string>(ObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search objects that are physically stored on the server
|
||||||
|
/// </summary
|
||||||
|
public class FilterByPhysicallyStored() : ObjectFilter<string>(ObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) {}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,9 @@ public class OwnerId(string id)
|
||||||
{
|
{
|
||||||
return Base58.Decode(Value);
|
return Base58.Decode(Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using FrostFS.SDK.Cryptography;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ public class SplitId
|
||||||
{
|
{
|
||||||
this.id = Guid.NewGuid();
|
this.id = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SplitId(Guid guid)
|
public SplitId(Guid guid)
|
||||||
{
|
{
|
||||||
this.id = guid;
|
this.id = guid;
|
||||||
|
@ -28,7 +30,7 @@ public class SplitId
|
||||||
public static SplitId CreateFromBinary(byte[] binaryData)
|
public static SplitId CreateFromBinary(byte[] binaryData)
|
||||||
{
|
{
|
||||||
return new SplitId(binaryData);
|
return new SplitId(binaryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SplitId CreateFromString(string stringData)
|
public static SplitId CreateFromString(string stringData)
|
||||||
{
|
{
|
||||||
|
@ -45,6 +47,6 @@ public class SplitId
|
||||||
if (this.id == Guid.Empty)
|
if (this.id == Guid.Empty)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return this.id.ToByteArray();
|
return this.id.ToBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1 +0,0 @@
|
||||||
namespace FrostFS.SDK.ModelsV2;
|
|
|
@ -21,7 +21,7 @@ public class SmokeTests
|
||||||
private static readonly PrmWait lightWait = new (100, 1);
|
private static readonly PrmWait lightWait = new (100, 1);
|
||||||
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
private readonly string url = "http://172.23.32.4:8080";
|
private readonly string url = "http://172.23.32.4:8080";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void NetworkMapTest()
|
public async void NetworkMapTest()
|
||||||
{
|
{
|
||||||
|
@ -115,7 +115,7 @@ public class SmokeTests
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
type: ObjectType.Regular,
|
type: ModelsV2.Enums.ObjectType.Regular,
|
||||||
new ObjectAttribute("fileName", "test")),
|
new ObjectAttribute("fileName", "test")),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
|
@ -140,6 +140,88 @@ public class SmokeTests
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void FilterTest()
|
||||||
|
{
|
||||||
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
|
||||||
|
await Cleanup(client);
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))))
|
||||||
|
{
|
||||||
|
WaitParams = lightWait
|
||||||
|
};
|
||||||
|
|
||||||
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
var bytes = new byte[] { 1, 2, 3 };
|
||||||
|
|
||||||
|
var ParentHeader = new ObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: ModelsV2.Enums.ObjectType.Regular)
|
||||||
|
{
|
||||||
|
PayloadLength = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new ObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: ModelsV2.Enums.ObjectType.Regular,
|
||||||
|
new ObjectAttribute("fileName", "test"))
|
||||||
|
{
|
||||||
|
Split = new Split(),
|
||||||
|
},
|
||||||
|
Payload = new MemoryStream(bytes),
|
||||||
|
ClientCut = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
|
||||||
|
|
||||||
|
var ecdsaKey = this.key.LoadWif();
|
||||||
|
|
||||||
|
var networkInfo = await client.GetNetmapSnapshotAsync();
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByContainerId(ObjectMatchType.Equals, containerId));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByOwnerId(ObjectMatchType.Equals, OwnerId.FromKey(ecdsaKey)));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterBySplitId(ObjectMatchType.Equals, param.Header.Split.SplitId));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test"));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByObjectId(ObjectMatchType.Equals, objectId));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByVersion(ObjectMatchType.Equals, networkInfo.NodeInfoCollection[0].Version));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByEpoch(ObjectMatchType.Equals, networkInfo.Epoch));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByPayloadLength(ObjectMatchType.Equals, 3));
|
||||||
|
|
||||||
|
var checkSum = CheckSum.CreateCheckSum(bytes);
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByPayloadHash(ObjectMatchType.Equals, checkSum));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByPhysicallyStored());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CheckFilter(IFrostFSClient client, ContainerId containerId, IObjectFilter filter)
|
||||||
|
{
|
||||||
|
var resultObjectsCount = 0;
|
||||||
|
|
||||||
|
PrmObjectSearch searchParam = new(containerId) { Filters = [filter] };
|
||||||
|
|
||||||
|
await foreach (var objId in client.SearchObjectsAsync(searchParam))
|
||||||
|
{
|
||||||
|
resultObjectsCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(1 == resultObjectsCount, $"Filter for {filter.Key} doesn't work");
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(1)]
|
[InlineData(1)]
|
||||||
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||||
|
@ -153,7 +235,7 @@ public class SmokeTests
|
||||||
bool callbackInvoked = false;
|
bool callbackInvoked = false;
|
||||||
var ctx = new Context
|
var ctx = new Context
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
// Timeout = TimeSpan.FromSeconds(20),
|
||||||
Callback = new((CallStatistics cs) =>
|
Callback = new((CallStatistics cs) =>
|
||||||
{
|
{
|
||||||
callbackInvoked = true;
|
callbackInvoked = true;
|
||||||
|
@ -179,7 +261,7 @@ public class SmokeTests
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
type: ObjectType.Regular,
|
type: ModelsV2.Enums.ObjectType.Regular,
|
||||||
new ObjectAttribute("fileName", "test")),
|
new ObjectAttribute("fileName", "test")),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
|
@ -191,7 +273,7 @@ public class SmokeTests
|
||||||
|
|
||||||
var objectId = await client.PutObjectAsync(param);
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
bool hasObject = false;
|
||||||
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] }))
|
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] }))
|
||||||
|
@ -263,7 +345,7 @@ public class SmokeTests
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
type: ObjectType.Regular,
|
type: ModelsV2.Enums.ObjectType.Regular,
|
||||||
new ObjectAttribute("fileName", "test")),
|
new ObjectAttribute("fileName", "test")),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = false,
|
ClientCut = false,
|
||||||
|
@ -276,7 +358,7 @@ public class SmokeTests
|
||||||
|
|
||||||
var objectId = await client.PutObjectAsync(param);
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
bool hasObject = false;
|
||||||
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter], SessionToken = token }))
|
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter], SessionToken = token }))
|
||||||
|
@ -319,6 +401,7 @@ public class SmokeTests
|
||||||
[InlineData(64 * 1024 * 1024 - 1)]
|
[InlineData(64 * 1024 * 1024 - 1)]
|
||||||
[InlineData(64 * 1024 * 1024 + 1)]
|
[InlineData(64 * 1024 * 1024 + 1)]
|
||||||
[InlineData(2 * 64 * 1024 * 1024 + 256)]
|
[InlineData(2 * 64 * 1024 * 1024 + 256)]
|
||||||
|
[InlineData(200)]
|
||||||
public async void ClientCutScenarioTest(int objectSize)
|
public async void ClientCutScenarioTest(int objectSize)
|
||||||
{
|
{
|
||||||
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||||
|
@ -348,7 +431,7 @@ public class SmokeTests
|
||||||
{
|
{
|
||||||
Header = new ObjectHeader(
|
Header = new ObjectHeader(
|
||||||
containerId: containerId,
|
containerId: containerId,
|
||||||
type: ObjectType.Regular,
|
type: ModelsV2.Enums.ObjectType.Regular,
|
||||||
new ObjectAttribute("fileName", "test")),
|
new ObjectAttribute("fileName", "test")),
|
||||||
Payload = new MemoryStream(bytes),
|
Payload = new MemoryStream(bytes),
|
||||||
ClientCut = true
|
ClientCut = true
|
||||||
|
@ -356,7 +439,7 @@ public class SmokeTests
|
||||||
|
|
||||||
var objectId = await client.PutObjectAsync(param);
|
var objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test");
|
var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
bool hasObject = false;
|
bool hasObject = false;
|
||||||
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
|
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter)))
|
||||||
|
@ -385,6 +468,8 @@ public class SmokeTests
|
||||||
|
|
||||||
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes));
|
||||||
|
|
||||||
|
await CheckFilter(client, containerId, new FilterByRootObject());
|
||||||
|
|
||||||
await Cleanup(client);
|
await Cleanup(client);
|
||||||
|
|
||||||
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
||||||
|
@ -392,14 +477,14 @@ public class SmokeTests
|
||||||
IAsyncEnumerator<ContainerId>? enumerator = null;
|
IAsyncEnumerator<ContainerId>? enumerator = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (deadline <= DateTime.UtcNow)
|
if (deadline <= DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
Assert.Fail("Containers exist");
|
Assert.Fail("Containers exist");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
enumerator = client.ListContainersAsync().GetAsyncEnumerator();
|
enumerator = client.ListContainersAsync().GetAsyncEnumerator();
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
}
|
}
|
||||||
while (await enumerator!.MoveNextAsync());
|
while (await enumerator!.MoveNextAsync());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue