[#30] Client: Add object model for Rules #42

Merged
orikik merged 1 commit from PavelGrossSpb/frostfs-sdk-csharp:apeRules into master 2025-02-13 07:05:01 +00:00
27 changed files with 677 additions and 155 deletions

View file

@ -0,0 +1,36 @@
namespace FrostFS.SDK.Client;
public struct Actions(bool inverted, string[] names) : System.IEquatable<Actions>
{
public bool Inverted { get; set; } = inverted;
public string[] Names { get; set; } = names;
public override bool Equals(object obj)
{
if (obj == null || obj is not Actions)
return false;
return Equals((Actions)obj);
}
public override readonly int GetHashCode()
{
return Inverted.GetHashCode() ^ string.Join(string.Empty, Names).GetHashCode();
}
public static bool operator ==(Actions left, Actions right)
{
return left.Equals(right);
}
public static bool operator !=(Actions left, Actions right)
{
return !(left == right);
}
public readonly bool Equals(Actions other)
{
return this.GetHashCode().Equals(other.GetHashCode());
}
}

View file

@ -0,0 +1,43 @@
namespace FrostFS.SDK.Client;
public struct Condition : System.IEquatable<Condition>
{
public ConditionType Op { get; set; }
public ConditionKindType Kind { get; set; }
public string? Key { get; set; }
public string? Value { get; set; }
public override bool Equals(object obj)
{
if (obj == null || obj is not Condition)
return false;
return Equals((Condition)obj);
}
public override readonly int GetHashCode()
{
return Op.GetHashCode()
^ Kind.GetHashCode()
^ (Key != null ? Key.GetHashCode() : 0)
^ (Value != null ? Value.GetHashCode() : 0);
}
public static bool operator ==(Condition left, Condition right)
{
return left.Equals(right);
}
public static bool operator !=(Condition left, Condition right)
{
return !(left == right);
}
public readonly bool Equals(Condition other)
{
return this.GetHashCode().Equals(other.GetHashCode());
}
}

View file

@ -0,0 +1,7 @@
namespace FrostFS.SDK.Client;
public enum ConditionKindType
{
Resource,
Request
}

View file

@ -0,0 +1,36 @@
namespace FrostFS.SDK.Client;
public enum ConditionType
{
CondStringEquals,
CondStringNotEquals,
CondStringEqualsIgnoreCase,
CondStringNotEqualsIgnoreCase,
CondStringLike,
CondStringNotLike,
CondStringLessThan,
CondStringLessThanEquals,
CondStringGreaterThan,
CondStringGreaterThanEquals,
// Numeric condition operators.
CondNumericEquals,
CondNumericNotEquals,
CondNumericLessThan,
CondNumericLessThanEquals,
CondNumericGreaterThan,
CondNumericGreaterThanEquals,
CondSliceContains,
CondIPAddress,
CondNotIPAddress,
}

View file

@ -2,7 +2,7 @@
public enum FrostFsTargetType
{
Undefined = 0,
Undefined,
Namespace,
Container,
User,

View file

@ -0,0 +1,9 @@
namespace FrostFS.SDK.Client;
public enum RuleStatus
{
Allow,
NoRuleFound,
AccessDenied,
QuotaLimitReached
}

View file

@ -0,0 +1,10 @@
namespace FrostFS.SDK.Client;
public class FrostFsChain
{
public byte[] ID { get; set; } = [];
public FrostFsRule[] Rules { get; set; } = [];
public RuleMatchType MatchType { get; set; }
}

View file

@ -0,0 +1,18 @@
namespace FrostFS.SDK.Client;
public class FrostFsRule
{
public RuleStatus Status { get; set; }
// Actions the operation is applied to.
public Actions Actions { get; set; }
// List of the resources the operation is applied to.
public Resource Resources { get; set; }
// True iff individual conditions must be combined with the logical OR.
// By default AND is used, so _each_ condition must pass.
public bool Any { get; set; }
public Condition[]? Conditions { get; set; }
}

View file

@ -0,0 +1,10 @@
namespace FrostFS.SDK.Client;
public enum RuleMatchType
{
// DenyPriority rejects the request if any `Deny` is specified.
DenyPriority,
// FirstMatch returns the first rule action matched to the request.
FirstMatch
}

View file

@ -0,0 +1,36 @@
namespace FrostFS.SDK.Client;
public struct Resource(bool inverted, string[] names) : System.IEquatable<Resource>
{
public bool Inverted { get; set; } = inverted;
public string[] Names { get; set; } = names;
public override bool Equals(object obj)
{
if (obj == null || obj is not Resource)
return false;
return Equals((Resource)obj);
}
public override readonly int GetHashCode()
{
return Inverted.GetHashCode() ^ string.Join(string.Empty, Names).GetHashCode();
}
public static bool operator ==(Resource left, Resource right)
{
return left.Equals(right);
}
public static bool operator !=(Resource left, Resource right)
{
return !(left == right);
}
public readonly bool Equals(Resource other)
{
return this.GetHashCode().Equals(other.GetHashCode());
}
}

View file

@ -0,0 +1,286 @@
using System;
namespace FrostFS.SDK.Client;
internal static class RuleSerializer
{
const byte Version = 0; // increase if breaking change
const int ByteSize = 1;
const int UInt8Size = ByteSize;
const int BoolSize = ByteSize;
const long NullSlice = -1;
const int NullSliceSize = 1;
const byte ByteTrue = 1;
const byte ByteFalse = 0;
/// <summary>
/// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77
/// </summary>
const int MaxSliceLen = 0x1000000;
const int ChainMarshalVersion = 0;
internal static byte[] Serialize(FrostFsChain chain)
{
int s = UInt8Size // Marshaller version
+ UInt8Size // Chain version
+ SliceSize(chain.ID, b => ByteSize)
+ SliceSize(chain.Rules, RuleSize)
+ UInt8Size; // MatchType
byte[] buf = new byte[s];
int offset = UInt8Marshal(buf, 0, Version);
offset = UInt8Marshal(buf, offset, ChainMarshalVersion);
offset = SliceMarshal(buf, offset, chain.ID, ByteMarshal);
offset = SliceMarshal(buf, offset, chain.Rules, MarshalRule);
offset = UInt8Marshal(buf, offset, (byte)chain.MatchType);
VerifyMarshal(buf, offset);
return buf;
}
private static int Int64Size(long value)
{
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=92;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
// and
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=41;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
ulong ux = (ulong)value << 1;
if (value < 0)
{
ux = ~ux;
}
int size = 0;
while (ux >= 0x80)
{
size++;
ux >>= 7;
}
return size + 1;
}
private static int SliceSize<T>(T[] slice, Func<T, int> sizeOf)
{
if (slice == null)
{
return NullSliceSize;
}
// Assuming Int64Size is the size of the slice
var size = Int64Size(slice.Length);
foreach (var v in slice)
{
size += sizeOf(v);
}
return size;
}
private static int StringSize(string? s)
{
var len = s !=null ? s.Length : 0;
return Int64Size(len) + len;
}
private static int ActionsSize(Actions action)
{
return BoolSize // Inverted
+ SliceSize(action.Names, StringSize);
}
private static int ResourcesSize(Resource resource)
{
return BoolSize // Inverted
+ SliceSize(resource.Names, StringSize);
}
private static int ConditionSize(Condition condition)
{
return ByteSize // Op
+ ByteSize // Object
+ StringSize(condition.Key)
+ StringSize(condition.Value);
}
public static int RuleSize(FrostFsRule rule)
{
if (rule is null)
{
throw new ArgumentNullException(nameof(rule));
}
return ByteSize // Status
+ ActionsSize(rule.Actions)
+ ResourcesSize(rule.Resources)
+ BoolSize // Any
+ SliceSize(rule.Conditions!, ConditionSize);
}
public static int UInt8Marshal(byte[] buf, int offset, byte value)
{
if (buf.Length - offset < 1)
{
throw new FrostFsException("Not enough bytes left to serialize value of type byte");
}
buf[offset] = value;
return offset + 1;
}
public static int ByteMarshal(byte[] buf, int offset, byte value)
{
return UInt8Marshal(buf, offset, value);
}
// PutVarint encodes an int64 into buf and returns the number of bytes written.
// If the buffer is too small, PutVarint will panic.
private static int PutVarint(byte[] buf, int offset, long x)
{
var ux = (ulong)x << 1;
if (x < 0)
{
ux = ~ux;
}
return PutUvarint(buf, offset, ux);
}
private static int PutUvarint(byte[] buf, int offset, ulong x)
{
while (x >= 0x80)
{
buf[offset] = (byte)(x | 0x80);
x >>= 7;
offset++;
}
buf[offset] = (byte)x;
return offset + 1;
}
public static int Int64Marshal(byte[] buf, int offset, long v)
{
if (buf.Length - offset < Int64Size(v))
{
throw new FrostFsException("Not enough bytes left to serialize value of type long");
}
return PutVarint(buf, offset, v);
}
public static int SliceMarshal<T>(byte[] buf, int offset, T[] slice, Func<byte[], int, T, int> marshalT)
{
if (slice == null)
{
return Int64Marshal(buf, offset, NullSlice);
}
if (slice.Length > MaxSliceLen)
{
throw new FrostFsException($"slice size if too big: {slice.Length}");
}
offset = Int64Marshal(buf, offset, slice.Length);
foreach (var v in slice)
{
offset = marshalT(buf, offset, v);
}
return offset;
}
private static int BoolMarshal(byte[] buf, int offset, bool value)
{
return UInt8Marshal(buf, offset, value ? ByteTrue : ByteFalse);
}
private static int StringMarshal(byte[] buf, int offset, string value)
{
if (value == null)
{
throw new FrostFsException($"string value is null");
}
if (value.Length > MaxSliceLen)
{
throw new FrostFsException($"string is too long: {value.Length}");
}
if (buf.Length - offset < Int64Size(value.Length) + value.Length)
{
throw new FrostFsException($"Not enough bytes left to serialize value of type string with length {value.Length}");
}
offset = Int64Marshal(buf, offset, value.Length);
if (string.IsNullOrEmpty(value))
{
return offset;
}
Buffer.BlockCopy(System.Text.Encoding.UTF8.GetBytes(value), 0, buf, offset, value.Length);
return offset + value.Length;
}
private static int MarshalActions(byte[] buf, int offset, Actions action)
{
offset = BoolMarshal(buf, offset, action.Inverted);
return SliceMarshal(buf, offset, action.Names, StringMarshal);
}
private static int MarshalCondition(byte[] buf, int offset, Condition condition)
{
offset = ByteMarshal(buf, offset, (byte)condition.Op);
offset = ByteMarshal(buf, offset, (byte)condition.Kind);
offset = StringMarshal(buf, offset, condition.Key!);
return StringMarshal(buf, offset, condition.Value!);
}
private static int MarshalRule(byte[] buf, int offset, FrostFsRule rule)
{
if (rule is null)
{
throw new ArgumentNullException(nameof(rule));
}
offset = ByteMarshal(buf, offset, (byte)rule.Status);
offset = MarshalActions(buf, offset, rule.Actions);
offset = MarshalResources(buf, offset, rule.Resources);
offset = BoolMarshal(buf, offset, rule.Any);
return SliceMarshal(buf, offset, rule.Conditions!, MarshalCondition);
}
private static int MarshalResources(byte[] buf, int offset, Resource resources)
{
offset = BoolMarshal(buf, offset, resources.Inverted);
return SliceMarshal(buf, offset, resources.Names, StringMarshal);
}
private static void VerifyMarshal(byte[] buf, int lastOffset)
{
if (buf.Length != lastOffset)
{
throw new FrostFsException("actual data size differs from expected");
}
}
}

View file

@ -10,7 +10,8 @@ public class FrostFsResponseException : FrostFsException
{
}
public FrostFsResponseException(FrostFsResponseStatus status)
public FrostFsResponseException(FrostFsResponseStatus status)
: base(status != null ? status.Message != null ? "" : "" : "")
{
Status = status;
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
namespace FrostFS.SDK.Client.Mappers.GRPC;
@ -13,6 +14,9 @@ public static class StatusMapper
return codeName is null
? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.")
: new FrostFsResponseStatus((FrostFsStatusCode)status.Code, status.Message);
: new FrostFsResponseStatus(
(FrostFsStatusCode)status.Code,
status.Message,
string.Join(", ", status.Details.Select(d => System.Text.Encoding.UTF8.GetString([.. d.Value]))));
}
}

View file

@ -1,41 +0,0 @@
using Google.Protobuf;
namespace FrostFS.SDK.Client;
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
{
private ByteString? grpcRaw;
public byte[] Raw { get; } = raw;
internal ByteString GetRaw()
{
return grpcRaw ??= ByteString.CopyFrom(Raw);
}
public override readonly bool Equals(object obj)
{
var chain = (FrostFsChain)obj;
return Equals(chain);
}
public override readonly int GetHashCode()
{
return Raw.GetHashCode();
}
public static bool operator ==(FrostFsChain left, FrostFsChain right)
{
return left.Equals(right);
}
public static bool operator !=(FrostFsChain left, FrostFsChain right)
{
return !(left == right);
}
public readonly bool Equals(FrostFsChain other)
{
return Raw == other.Raw;
}
}

View file

@ -34,21 +34,18 @@ public class FrostFsContainerId
throw new FrostFsInvalidObjectException();
}
internal ContainerID ContainerID
public ContainerID GetContainerID()
{
get
if (this.containerID != null)
return this.containerID;
if (modelId != null)
{
if (this.containerID != null)
return this.containerID;
if (modelId != null)
{
this.containerID = this.ToMessage();
return this.containerID;
}
throw new FrostFsInvalidObjectException();
this.containerID = this.ToMessage();
return this.containerID;
}
throw new FrostFsInvalidObjectException();
}
public override string ToString()

View file

@ -1,14 +1,16 @@
namespace FrostFS.SDK;
public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null)
public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null, string? details = null)
{
public FrostFsStatusCode Code { get; set; } = code;
public string Message { get; set; } = message ?? string.Empty;
public string Details { get; set; } = details ?? string.Empty;
public bool IsSuccess => Code == FrostFsStatusCode.Success;
public override string ToString()
{
return $"Response status: {Code}. Message: {Message}.";
return $"Response status: {Code}. Message: {Message}. Details: {Details}";
}
}

View file

@ -1,4 +1,6 @@
namespace FrostFS.SDK.Client;
using FrostFS.SDK.Client;
namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable<PrmApeChainAdd>
{

View file

@ -1,4 +1,6 @@
namespace FrostFS.SDK.Client;
using FrostFS.SDK.Client;
namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainList(FrostFsChainTarget target, string[]? xheaders = null) : System.IEquatable<PrmApeChainList>
{

View file

@ -1,13 +1,16 @@
namespace FrostFS.SDK.Client;
using System;
using FrostFS.SDK.Client;
namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainRemove(
FrostFsChainTarget target,
FrostFsChain chain,
byte[] chainId,
string[]? xheaders = null) : System.IEquatable<PrmApeChainRemove>
{
public FrostFsChainTarget Target { get; } = target;
public FrostFsChain Chain { get; } = chain;
public byte[] ChainId { get; } = chainId;
/// <summary>
/// FrostFS request X-Headers
@ -25,13 +28,13 @@ public readonly struct PrmApeChainRemove(
public readonly bool Equals(PrmApeChainRemove other)
{
return Target == other.Target
&& Chain == other.Chain
&& ChainId.Equals(other.ChainId)
&& XHeaders == other.XHeaders;
}
public override readonly int GetHashCode()
{
return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode();
return ChainId.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode();
}
public static bool operator ==(PrmApeChainRemove left, PrmApeChainRemove right)

View file

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Frostfs.V2.Ape;
using Frostfs.V2.Apemanager;
using Google.Protobuf;
namespace FrostFS.SDK.Client.Services;
@ -18,11 +19,15 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
internal async Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args, CallContext ctx)
{
var binary = RuleSerializer.Serialize(args.Chain);
var base64 = Convert.ToBase64String(binary);
AddChainRequest request = new()
{
Body = new()
{
Chain = new() { Raw = args.Chain.GetRaw() },
Chain = new() { Raw = UnsafeByteOperations.UnsafeWrap(binary) },
Target = args.Target.GetChainTarget()
}
};
@ -43,7 +48,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
{
Body = new()
{
ChainId = args.Chain.GetRaw(),
ChainId = UnsafeByteOperations.UnsafeWrap(args.ChainId),
Target = args.Target.GetChainTarget()
}
};

View file

@ -39,7 +39,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService
internal async Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx)
{
GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, ClientContext.Key.ECDsaKey);
GetRequest request = GetContainerRequest(args.Container.GetContainerID(), args.XHeaders, ClientContext.Key.ECDsaKey);
var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken);

View file

@ -52,7 +52,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
{
Address = new Address
{
ContainerId = args.ContainerId.ContainerID,
ContainerId = args.ContainerId.GetContainerID(),
ObjectId = args.ObjectId.ToMessage()
},
Raw = args.Raw
@ -435,7 +435,10 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
// send the last part and create linkObject
if (sentObjectIds.Count > 0)
{
var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, [.. attributes])
var largeObjectHeader = new FrostFsObjectHeader(
header.ContainerId,
FrostFsObjectType.Regular,
attributes != null ? [.. attributes] : [])
{
PayloadLength = args.PutObjectContext.FullLength,
};
@ -581,7 +584,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
}
};
var sessionToken = (args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false));
var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false);
var protoToken = sessionToken.CreateObjectTokenContext(
new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid },

View file

@ -88,7 +88,6 @@ public static class Verifier
return key.VerifyData(data2Verify, sig.Sign.ToByteArray());
}
internal static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification)
{
if (!verification.MetaSignature.VerifyMessagePart(meta))
@ -118,12 +117,19 @@ public static class Verifier
internal static void CheckResponse(IResponse resp)
{
if (!resp.Verify())
{
throw new FormatException($"invalid response, type={resp.GetType()}");
}
var status = resp.MetaHeader.Status.ToModel();
if (resp.MetaHeader != null)
{
var status = resp.MetaHeader.Status.ToModel();
if (status != null && !status.IsSuccess)
throw new FrostFsResponseException(status);
if (status != null && !status.IsSuccess)
{
throw new FrostFsResponseException(status);
}
}
}
/// <summary>
@ -138,6 +144,8 @@ public static class Verifier
}
if (!request.Verify())
{
throw new FrostFsResponseException($"invalid response, type={request.GetType()}");
}
}
}

View file

@ -1,11 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using FrostFS.Refs;
using FrostFS.SDK.Client;
using FrostFS.SDK.Client.Interfaces;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.SmokeTests;
using Google.Protobuf;
using Microsoft.Extensions.Options;
namespace FrostFS.SDK.Tests.Smoke;
@ -38,8 +40,8 @@ public class SmokeClientTests : SmokeTestsBase
Assert.Equal(13, result.Version.Minor);
Assert.Equal(NodeState.Online, result.State);
Assert.Equal(33, result.PublicKey.Length);
Assert.Single(result.Addresses);
Assert.Equal(9, result.Attributes.Count);
// Assert.Single(result.Addresses);
// Assert.Equal(9, result.Attributes.Count);
}
[Fact]
@ -81,12 +83,16 @@ public class SmokeClientTests : SmokeTestsBase
var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams,
xheaders: ["key1", "value1"]);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
await AddObjectRules(client, containerId);
var bytes = GetRandomBytes(1024);
var param = new PrmObjectPut(
@ -126,34 +132,29 @@ public class SmokeClientTests : SmokeTestsBase
await Cleanup(client);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
lightWait);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
await AddObjectRules(client, containerId);
var bytes = new byte[] { 1, 2, 3 };
var ParentHeader = new FrostFsObjectHeader(
containerId: containerId,
type: FrostFsObjectType.Regular)
{
PayloadLength = 3
};
var param = new PrmObjectPut(
new FrostFsObjectHeader(
containerId: containerId,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")],
new FrostFsSplit()));
new FrostFsObjectHeader(
containerId: containerId,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")],
new FrostFsSplit()));
var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true);
await stream.WriteAsync(bytes.AsMemory());
var objectId = await stream.CompleteAsync();
var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default);
var ecdsaKey = keyString.LoadWif();
var networkInfo = await client.GetNetmapSnapshotAsync(default);
@ -176,7 +177,7 @@ public class SmokeClientTests : SmokeTestsBase
var checkSum = CheckSum.CreateCheckSum(bytes);
await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum));
// await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum));
await CheckFilter(client, containerId, new FilterByPhysicallyStored());
}
@ -219,21 +220,25 @@ public class SmokeClientTests : SmokeTestsBase
var ctx = new CallContext(TimeSpan.FromSeconds(20));
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams,
xheaders: ["testKey", "testValue"]);
var createdContainer = await client.CreateContainerAsync(createContainerParam, ctx);
var containerId = await client.CreateContainerAsync(createContainerParam, ctx);
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default);
Assert.NotNull(container);
Assert.True(callbackInvoked);
await AddObjectRules(client, containerId);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut(
new FrostFsObjectHeader(
containerId: createdContainer,
containerId: containerId,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]));
@ -243,22 +248,19 @@ public class SmokeClientTests : SmokeTestsBase
var objectId = await stream.CompleteAsync();
var filter1 = new FilterByOwnerId(FrostFsMatchType.Equals, OwnerId!);
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter1), default))
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter1), default))
{
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId, false), default);
var objHeader1 = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId, true), default);
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default);
}
var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
bool hasObject = false;
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default))
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default))
{
hasObject = true;
var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default);
var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default);
var objHeader = res.HeaderInfo;
Assert.NotNull(objHeader);
@ -271,7 +273,7 @@ public class SmokeClientTests : SmokeTestsBase
Assert.True(hasObject);
var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default);
var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default);
var downloadedBytes = new byte[@object.Header.PayloadLength];
MemoryStream ms = new(downloadedBytes);
@ -300,15 +302,19 @@ public class SmokeClientTests : SmokeTestsBase
await Cleanup(client);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams,
xheaders: ["testKey", "testValue"]);
var createdContainer = await client.CreateContainerAsync(createContainerParam, default);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default);
Assert.NotNull(container);
await AddObjectRules(client, containerId);
var bytes = new byte[1024];
for (int i = 0; i < 1024; i++)
{
@ -317,7 +323,7 @@ public class SmokeClientTests : SmokeTestsBase
var param = new PrmObjectPut(
new FrostFsObjectHeader(
containerId: createdContainer,
containerId: containerId,
type: FrostFsObjectType.Regular,
[new FrostFsAttributePair("fileName", "test")]));
@ -335,14 +341,14 @@ public class SmokeClientTests : SmokeTestsBase
var range = new FrostFsRange(64, (ulong)patch.Length);
var patchParams = new PrmObjectPatch(
new FrostFsAddress(createdContainer, objectId),
new FrostFsAddress(containerId, objectId),
payload: new MemoryStream(patch),
maxChunkLength: 256,
range: range);
var newIbjId = await client.PatchObjectAsync(patchParams, default);
var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId), default);
var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, newIbjId), default);
var downloadedBytes = new byte[@object.Header.PayloadLength];
MemoryStream ms = new(downloadedBytes);
@ -380,15 +386,19 @@ public class SmokeClientTests : SmokeTestsBase
await Cleanup(client);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams,
xheaders: ["testKey", "testValue"]);
var createdContainer = await client.CreateContainerAsync(createContainerParam, default);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default);
Assert.NotNull(container);
await AddObjectRules(client, containerId);
var bytes = new byte[256];
for (int i = 0; i < 256; i++)
{
@ -397,7 +407,7 @@ public class SmokeClientTests : SmokeTestsBase
var param = new PrmObjectPut(
new FrostFsObjectHeader(
containerId: createdContainer,
containerId: containerId,
type: FrostFsObjectType.Regular));
var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true);
@ -405,7 +415,7 @@ public class SmokeClientTests : SmokeTestsBase
await stream.WriteAsync(bytes.AsMemory());
var objectId = await stream.CompleteAsync();
var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(50, 100));
var rangeParam = new PrmRangeGet(containerId, objectId, new FrostFsRange(50, 100));
var rangeReader = await client.GetRangeAsync(rangeParam, default);
@ -436,15 +446,19 @@ public class SmokeClientTests : SmokeTestsBase
await Cleanup(client);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams,
xheaders: ["testKey", "testValue"]);
var createdContainer = await client.CreateContainerAsync(createContainerParam, default);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default);
var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default);
Assert.NotNull(container);
await AddObjectRules(client, containerId);
var bytes = new byte[256];
for (int i = 0; i < 256; i++)
{
@ -453,7 +467,7 @@ public class SmokeClientTests : SmokeTestsBase
var param = new PrmObjectPut(
new FrostFsObjectHeader(
containerId: createdContainer,
containerId: containerId,
type: FrostFsObjectType.Regular));
var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true);
@ -461,7 +475,7 @@ public class SmokeClientTests : SmokeTestsBase
await stream.WriteAsync(bytes.AsMemory());
var objectId = await stream.CompleteAsync();
var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes);
var rangeParam = new PrmRangeHashGet(containerId, objectId, [new FrostFsRange(100, 64)], bytes);
var hashes = await client.GetRangeHashAsync(rangeParam, default);
@ -493,7 +507,9 @@ public class SmokeClientTests : SmokeTestsBase
var ctx = new CallContext(TimeSpan.FromSeconds(20));
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
PrmWait.DefaultParams);
var container = await client.CreateContainerAsync(createContainerParam, ctx);
@ -501,6 +517,8 @@ public class SmokeClientTests : SmokeTestsBase
var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx);
Assert.NotNull(containerInfo);
await AddObjectRules(client, container);
var bytes = GetRandomBytes(objectSize);
var param = new PrmObjectPut(
@ -573,7 +591,9 @@ public class SmokeClientTests : SmokeTestsBase
await Cleanup(client);
var createContainerParam = new PrmContainerCreate(
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))),
new FrostFsContainerInfo(
new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)),
[new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]),
lightWait);
var containerId = await client.CreateContainerAsync(createContainerParam, default);
@ -584,6 +604,8 @@ public class SmokeClientTests : SmokeTestsBase
Assert.NotNull(container);
await AddObjectRules(client, containerId);
byte[] bytes = GetRandomBytes(objectSize);
var param = new PrmObjectClientCutPut(
@ -595,29 +617,10 @@ public class SmokeClientTests : SmokeTestsBase
var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true);
// var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test");
//bool hasObject = false;
//await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default))
//{
// hasObject = true;
// var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default);
// Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
// Assert.NotNull(objHeader.Attributes);
// Assert.Single(objHeader.Attributes);
// Assert.Equal("fileName", objHeader.Attributes[0].Key);
// Assert.Equal("test", objHeader.Attributes[0].Value);
//}
//Assert.True(hasObject);
var filter1 = new FilterByOwnerId(FrostFsMatchType.Equals, OwnerId!);
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter1), default))
{
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default);
var objHeader1 = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, true), default);
}
var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default);
@ -658,7 +661,7 @@ public class SmokeClientTests : SmokeTestsBase
public async void NodeInfoCallbackAndInterceptorTest()
{
bool callbackInvoked = false;
bool intercepterInvoked = false;
bool interceptorInvoked = false;
var options = ClientOptions;
options.Value.Callback = (cs) =>
@ -667,21 +670,21 @@ public class SmokeClientTests : SmokeTestsBase
Assert.True(cs.ElapsedMicroSeconds > 0);
};
options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true));
options.Value.Interceptors.Add(new CallbackInterceptor(s => interceptorInvoked = true));
var client = FrostFSClient.GetInstance(options, GrpcChannel);
var result = await client.GetNodeInfoAsync(default);
Assert.True(callbackInvoked);
Assert.True(intercepterInvoked);
Assert.True(interceptorInvoked);
Assert.Equal(2, result.Version.Major);
Assert.Equal(13, result.Version.Minor);
Assert.Equal(NodeState.Online, result.State);
Assert.Equal(33, result.PublicKey.Length);
Assert.Single(result.Addresses);
Assert.Equal(9, result.Attributes.Count);
Assert.NotNull(result.Addresses);
Assert.True(result.Attributes.Count > 0);
}
private static byte[] GetRandomBytes(int size)
@ -701,7 +704,6 @@ public class SmokeClientTests : SmokeTestsBase
});
}
static async Task Cleanup(IFrostFSClient client)
{
await foreach (var cid in client.ListContainersAsync(default, default))
@ -709,4 +711,41 @@ public class SmokeClientTests : SmokeTestsBase
await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default);
}
}
private static async Task AddObjectRules(IFrostFSClient client, FrostFsContainerId containerId)
{
var addChainPrm = new PrmApeChainAdd(
new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()),
new FrostFsChain
{
ID = Encoding.ASCII.GetBytes("chain-id-test"),
Rules = [
new FrostFsRule
{
Status = RuleStatus.Allow,
Actions = new Actions(inverted: false, names: ["*"]),
Resources = new Resource (inverted: false, names: [$"native:object/*"]),
Any = false,
Conditions = []
}
],
MatchType = RuleMatchType.DenyPriority
}
);
await client.AddChainAsync(addChainPrm, default);
var listChainPrm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()));
while (true)
{
await Task.Delay(1000);
var chains = await client.ListChainAsync(listChainPrm, default);
if (chains.Length > 0)
break;
}
await Task.Delay(8000);
}
}

View file

@ -11,9 +11,15 @@ namespace FrostFS.SDK.Tests.Smoke;
public abstract class SmokeTestsBase
{
internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
// cluster
internal readonly string url = "http://10.78.128.190:8080";
internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba";
internal readonly string url = "http://172.23.32.4:8080";
// WSL2
//internal readonly string url = "http://172.29.238.97:8080";
//internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK";
//"KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
protected ECDsa? Key { get; }

View file

@ -35,7 +35,7 @@ public class PlacementVectorTests(ITestOutputHelper testOutputHelper)
//if (!file.EndsWith("selector_invalid.json"))
// continue;
var fileName = file[(file.LastIndexOf("..\\") + 3)..];
var fileName = file[(file.LastIndexOf("..\\", StringComparison.OrdinalIgnoreCase) + 3)..];
_testOutputHelper.WriteLine($"Open file {fileName}");
var str = File.ReadAllText(file);