[#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 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
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ public static class ObjectFilterMapper
|
|||
{
|
||||
MatchType = objMatchTypeName,
|
||||
Key = filter.Key,
|
||||
Value = filter.Value
|
||||
Value = filter.GetSerializedValue()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public static class ObjectHeaderMapper
|
|||
|
||||
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(),
|
||||
ParentHeader = header.Split.ParentHeader?.ToModel(),
|
||||
|
@ -86,5 +86,5 @@ public static class ObjectHeaderMapper
|
|||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ namespace FrostFS.SDK.ClientV2.Parameters;
|
|||
|
||||
public sealed class PrmContainerGetAll() : IContext
|
||||
{
|
||||
public string SessionToken { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// FrostFS request X-Headers
|
||||
/// </summary>
|
||||
|
|
|
@ -3,15 +3,19 @@ using System.Collections.Specialized;
|
|||
using FrostFS.SDK.ModelsV2;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the search criteria
|
||||
/// </summary>
|
||||
/// <value>Collection of filters</value>
|
||||
public IEnumerable<ObjectFilter> Filters { get; set; } = filters;
|
||||
public IEnumerable<IObjectFilter> Filters { get; set; } = filters;
|
||||
|
||||
/// <summary>
|
||||
/// 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.Container;
|
||||
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using System.Collections.Generic;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
using FrostFS.Refs;
|
||||
using System;
|
||||
using FrostFS.SDK.ClientV2.Parameters;
|
||||
using System.Collections.Specialized;
|
||||
using FrostFS.Refs;
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -49,7 +41,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
request.AddMetaHeader(args.XHeaders);
|
||||
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);
|
||||
|
||||
|
@ -74,11 +66,11 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
Signature = Context.Key.SignRFC6979(grpcContainer)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
request.AddMetaHeader(args.XHeaders);
|
||||
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);
|
||||
|
||||
|
@ -98,11 +90,12 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
request.AddMetaHeader(args.XHeaders);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -137,7 +130,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -148,7 +141,7 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
Func<Task> action,
|
||||
WaitExpects expect,
|
||||
PrmWait? waitParams)
|
||||
{
|
||||
{
|
||||
waitParams ??= PrmWait.DefaultParams;
|
||||
var deadLine = waitParams.GetDeadline();
|
||||
|
||||
|
@ -160,7 +153,10 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
|
||||
if (expect == WaitExpects.Exists)
|
||||
return;
|
||||
|
||||
|
||||
if (DateTime.UtcNow >= deadLine)
|
||||
throw new TimeoutException();
|
||||
|
||||
await Task.Delay(waitParams.PollInterval);
|
||||
}
|
||||
catch (ResponseException ex)
|
||||
|
@ -173,11 +169,9 @@ internal class ContainerServiceProvider : ContextAccessor
|
|||
|
||||
if (expect == WaitExpects.Removed)
|
||||
return;
|
||||
|
||||
|
||||
await Task.Delay(waitParams.PollInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,14 +12,19 @@ using FrostFS.SDK.Cryptography;
|
|||
using FrostFS.Session;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
using FrostFS.SDK.ClientV2.Extensions;
|
||||
using System.Threading;
|
||||
using FrostFS.SDK.ClientV2.Parameters;
|
||||
|
||||
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 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)
|
||||
{
|
||||
|
@ -137,7 +142,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
|||
Context.Key);
|
||||
|
||||
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||
|
||||
|
||||
request.Sign(Context.Key);
|
||||
|
||||
var objectsIds = SearchObjects(request, ctx);
|
||||
|
@ -192,8 +197,6 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
|||
|
||||
return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
||||
}
|
||||
|
||||
static readonly AsyncLocal<Session.SessionToken> asyncLocalSession = new ();
|
||||
|
||||
private async Task<ObjectId> PutClientCutObject(PrmObjectPut args)
|
||||
{
|
||||
|
@ -274,7 +277,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
|||
return tools.CalculateObjectId(largeObject.Header);
|
||||
}
|
||||
|
||||
currentObject.AddAttributes(args.Header!.Attributes);
|
||||
currentObject
|
||||
.SetSplit(null)
|
||||
.AddAttributes(args.Header!.Attributes);
|
||||
|
||||
return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx));
|
||||
}
|
||||
|
@ -421,14 +426,4 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
public static FrostFsObject SetSplit(this FrostFsObject obj, Split split)
|
||||
public static FrostFsObject SetSplit(this FrostFsObject obj, Split? split)
|
||||
{
|
||||
obj.Header.Split = split;
|
||||
return obj;
|
||||
|
|
|
@ -80,9 +80,7 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx)
|
|||
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
|
||||
grpcHeader.Version = Context.Version.ToGrpcMessage();
|
||||
|
||||
if (header.PayloadCheckSum != null)
|
||||
grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum);
|
||||
else if (payload != null)
|
||||
if (payload != null)
|
||||
grpcHeader.PayloadHash = Sha256Checksum(payload);
|
||||
|
||||
return grpcHeader;
|
||||
|
|
|
@ -7,18 +7,49 @@ public static class UUIDExtension
|
|||
{
|
||||
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)
|
||||
{
|
||||
var str = id.ToString("N");
|
||||
var len = str.Length;
|
||||
var bytes = new byte[len/2];
|
||||
var bytes = id.ToByteArray();
|
||||
|
||||
for (int i = 0; i < len; i += 2)
|
||||
bytes[i/2] = Convert.ToByte(str.Substring(i, 2), 16);
|
||||
|
||||
return bytes;
|
||||
var orderedBytes = GetGuidBytesDirectOrder(bytes);
|
||||
|
||||
return orderedBytes;
|
||||
}
|
||||
|
||||
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.Text;
|
||||
|
||||
namespace FrostFS.SDK.ModelsV2;
|
||||
|
||||
public class CheckSum
|
||||
{
|
||||
// type is always Sha256
|
||||
public byte[]? Hash { get; set; }
|
||||
|
||||
public static byte[] GetHash(byte[] content)
|
||||
|
@ -20,6 +21,6 @@ public class CheckSum
|
|||
|
||||
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();
|
||||
|
||||
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
|
||||
{
|
||||
get { return Header!.PayloadLength; }
|
||||
|
|
|
@ -2,37 +2,112 @@ using FrostFS.SDK.ModelsV2.Enums;
|
|||
|
||||
namespace FrostFS.SDK.ModelsV2;
|
||||
|
||||
public class ObjectFilter
|
||||
public interface IObjectFilter
|
||||
{
|
||||
private const string HeaderPrefix = "$Object:";
|
||||
public ObjectMatchType MatchType { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
string? GetSerializedValue();
|
||||
}
|
||||
|
||||
public ObjectFilter(ObjectMatchType matchType, string key, string value)
|
||||
{
|
||||
MatchType = matchType;
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
public abstract class ObjectFilter<T>(ObjectMatchType matchType, string key, T value) : IObjectFilter
|
||||
{
|
||||
public ObjectMatchType MatchType { get; set; } = matchType;
|
||||
public string Key { get; set; } = key;
|
||||
|
||||
public static ObjectFilter ObjectIdFilter(ObjectMatchType matchType, ObjectId objectId)
|
||||
{
|
||||
return new ObjectFilter(matchType, HeaderPrefix + "objectID", objectId.Value);
|
||||
}
|
||||
public T Value { get; set; } = value;
|
||||
|
||||
public static ObjectFilter OwnerFilter(ObjectMatchType matchType, OwnerId ownerId)
|
||||
public string? GetSerializedValue()
|
||||
{
|
||||
return new ObjectFilter(matchType, HeaderPrefix + "ownerID", ownerId.Value);
|
||||
}
|
||||
|
||||
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());
|
||||
return Value?.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);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using System;
|
||||
|
||||
namespace FrostFS.SDK.ModelsV2;
|
||||
|
||||
|
@ -10,6 +11,7 @@ public class SplitId
|
|||
{
|
||||
this.id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public SplitId(Guid guid)
|
||||
{
|
||||
this.id = guid;
|
||||
|
@ -28,7 +30,7 @@ public class SplitId
|
|||
public static SplitId CreateFromBinary(byte[] binaryData)
|
||||
{
|
||||
return new SplitId(binaryData);
|
||||
}
|
||||
}
|
||||
|
||||
public static SplitId CreateFromString(string stringData)
|
||||
{
|
||||
|
@ -45,6 +47,6 @@ public class SplitId
|
|||
if (this.id == Guid.Empty)
|
||||
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 readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||
private readonly string url = "http://172.23.32.4:8080";
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void NetworkMapTest()
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ public class SmokeTests
|
|||
{
|
||||
Header = new ObjectHeader(
|
||||
containerId: containerId,
|
||||
type: ObjectType.Regular,
|
||||
type: ModelsV2.Enums.ObjectType.Regular,
|
||||
new ObjectAttribute("fileName", "test")),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false,
|
||||
|
@ -140,6 +140,88 @@ public class SmokeTests
|
|||
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]
|
||||
[InlineData(1)]
|
||||
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||
|
@ -153,7 +235,7 @@ public class SmokeTests
|
|||
bool callbackInvoked = false;
|
||||
var ctx = new Context
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(20),
|
||||
// Timeout = TimeSpan.FromSeconds(20),
|
||||
Callback = new((CallStatistics cs) =>
|
||||
{
|
||||
callbackInvoked = true;
|
||||
|
@ -179,7 +261,7 @@ public class SmokeTests
|
|||
{
|
||||
Header = new ObjectHeader(
|
||||
containerId: containerId,
|
||||
type: ObjectType.Regular,
|
||||
type: ModelsV2.Enums.ObjectType.Regular,
|
||||
new ObjectAttribute("fileName", "test")),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false,
|
||||
|
@ -191,7 +273,7 @@ public class SmokeTests
|
|||
|
||||
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;
|
||||
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] }))
|
||||
|
@ -263,7 +345,7 @@ public class SmokeTests
|
|||
{
|
||||
Header = new ObjectHeader(
|
||||
containerId: containerId,
|
||||
type: ObjectType.Regular,
|
||||
type: ModelsV2.Enums.ObjectType.Regular,
|
||||
new ObjectAttribute("fileName", "test")),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false,
|
||||
|
@ -276,7 +358,7 @@ public class SmokeTests
|
|||
|
||||
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;
|
||||
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(2 * 64 * 1024 * 1024 + 256)]
|
||||
[InlineData(200)]
|
||||
public async void ClientCutScenarioTest(int objectSize)
|
||||
{
|
||||
using var client = Client.GetInstance(GetOptions(this.key, this.url));
|
||||
|
@ -348,7 +431,7 @@ public class SmokeTests
|
|||
{
|
||||
Header = new ObjectHeader(
|
||||
containerId: containerId,
|
||||
type: ObjectType.Regular,
|
||||
type: ModelsV2.Enums.ObjectType.Regular,
|
||||
new ObjectAttribute("fileName", "test")),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = true
|
||||
|
@ -356,7 +439,7 @@ public class SmokeTests
|
|||
|
||||
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;
|
||||
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));
|
||||
|
||||
await CheckFilter(client, containerId, new FilterByRootObject());
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
|
||||
|
@ -392,14 +477,14 @@ public class SmokeTests
|
|||
IAsyncEnumerator<ContainerId>? enumerator = null;
|
||||
do
|
||||
{
|
||||
if (deadline <= DateTime.UtcNow)
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
break;
|
||||
}
|
||||
if (deadline <= DateTime.UtcNow)
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
break;
|
||||
}
|
||||
|
||||
enumerator = client.ListContainersAsync().GetAsyncEnumerator();
|
||||
await Task.Delay(500);
|
||||
enumerator = client.ListContainersAsync().GetAsyncEnumerator();
|
||||
await Task.Delay(500);
|
||||
}
|
||||
while (await enumerator!.MoveNextAsync());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue