[#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);
dstepanov-yadro marked this conversation as resolved Outdated

Null slice must have size 1

Null slice must have size 1

Seems that Golang SDK is not protected from null Action or Resources and panics in these cases. So, null Action/Resourse makes the rule invalid. I've fixed the code accordingly.

Seems that Golang SDK is not protected from null Action or Resources and panics in these cases. So, null Action/Resourse makes the rule invalid. I've fixed the code accordingly.

Golang SDK uses struct. It is value type, so it can't be nil.

Golang SDK uses struct. It is value type, so it can't be nil.

You can do the same by using struct instead of class

You can do the same by using `struct` instead of `class`

OK, let it be structs. Done.

OK, let it be structs. Done.
}
private static int ResourcesSize(Resource resource)
{
return BoolSize // Inverted
+ SliceSize(resource.Names, StringSize);
}
dstepanov-yadro marked this conversation as resolved Outdated

Also, if null, then size must be 1. Some unit tests required.

Also, if null, then size must be 1. Some unit tests required.

Fixed, see previous comment

Fixed, see previous comment

Great! What do you think about some positive/negative unit tests for serializer?

Great! What do you think about some positive/negative unit tests for serializer?

I'm working on it, but we've committed to provide this functionality today. So the tests will be some later.

I'm working on it, but we've committed to provide this functionality today. So the tests will be some later.

Ok, will test on production. Hardcore!

Ok, will test on production. Hardcore!
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

@ -11,6 +11,7 @@ public class FrostFsResponseException : FrostFsException
}
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,9 +34,7 @@ public class FrostFsContainerId
throw new FrostFsInvalidObjectException();
}
internal ContainerID ContainerID
{
get
public ContainerID GetContainerID()
{
if (this.containerID != null)
return this.containerID;
@ -49,7 +47,6 @@ public class FrostFsContainerId
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,13 +117,20 @@ public static class Verifier
internal static void CheckResponse(IResponse resp)
{
if (!resp.Verify())
{
throw new FormatException($"invalid response, type={resp.GetType()}");
}
if (resp.MetaHeader != null)
{
var status = resp.MetaHeader.Status.ToModel();
if (status != null && !status.IsSuccess)
{
throw new FrostFsResponseException(status);
}
}
}
/// <summary>
/// This method is intended for unit tests for request verification.
@ -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,19 +132,16 @@ 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);
var bytes = new byte[] { 1, 2, 3 };
await AddObjectRules(client, containerId);
var ParentHeader = new FrostFsObjectHeader(
containerId: containerId,
type: FrostFsObjectType.Regular)
{
PayloadLength = 3
};
var bytes = new byte[] { 1, 2, 3 };
var param = new PrmObjectPut(
new FrostFsObjectHeader(
@ -152,8 +155,6 @@ public class SmokeClientTests : SmokeTestsBase
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);